flowcommerce_spree 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +91 -0
- data/Rakefile +33 -0
- data/SPREE_FLOW.md +134 -0
- data/app/assets/javascripts/flowcommerce_spree/application.js +13 -0
- data/app/assets/stylesheets/flowcommerce_spree/application.css +15 -0
- data/app/controllers/concerns/current_zone_loader_decorator.rb +49 -0
- data/app/controllers/flowcommerce_spree/webhooks_controller.rb +25 -0
- data/app/helpers/flowcommerce_spree/application_helper.rb +6 -0
- data/app/helpers/spree/admin/orders_helper_decorator.rb +17 -0
- data/app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb +53 -0
- data/app/mailers/spree/spree_order_mailer_decorator.rb +24 -0
- data/app/models/flowcommerce_spree/settings.rb +8 -0
- data/app/models/spree/credit_card_decorator.rb +9 -0
- data/app/models/spree/flow_io_product_decorator.rb +91 -0
- data/app/models/spree/flow_io_variant_decorator.rb +205 -0
- data/app/models/spree/gateway/spree_flow_gateway.rb +116 -0
- data/app/models/spree/line_item_decorator.rb +15 -0
- data/app/models/spree/order_decorator.rb +179 -0
- data/app/models/spree/promotion_decorator.rb +10 -0
- data/app/models/spree/promotion_handler/coupon_decorator.rb +30 -0
- data/app/models/spree/spree_user_decorator.rb +15 -0
- data/app/models/spree/taxon_decorator.rb +37 -0
- data/app/models/spree/zone_decorator.rb +7 -0
- data/app/models/spree/zones/flow_io_product_zone_decorator.rb +55 -0
- data/app/services/flowcommerce_spree/import_experience_items.rb +76 -0
- data/app/services/flowcommerce_spree/import_experiences.rb +37 -0
- data/app/services/flowcommerce_spree/order_sync.rb +231 -0
- data/app/views/layouts/flowcommerce_spree/application.html.erb +14 -0
- data/app/views/spree/admin/payments/index.html.erb +28 -0
- data/app/views/spree/admin/promotions/edit.html.erb +57 -0
- data/app/views/spree/admin/shared/_order_summary.html.erb +44 -0
- data/app/views/spree/admin/shared/_order_summary_flow.html.erb +13 -0
- data/app/views/spree/order_mailer/confirm_email.html.erb +86 -0
- data/app/views/spree/order_mailer/confirm_email.text.erb +38 -0
- data/config/initializers/flowcommerce_spree.rb +7 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20201021160159_add_type_and_meta_to_spree_zone.rb +23 -0
- data/db/migrate/20201021755957_add_meta_to_spree_tables.rb +17 -0
- data/db/migrate/20201022173210_add_zone_type_to_spree_zone_members.rb +24 -0
- data/db/migrate/20201022174252_add_kind_to_zone.rb +22 -0
- data/lib/flow/error.rb +73 -0
- data/lib/flow/pay_pal.rb +25 -0
- data/lib/flow/simple_gateway.rb +115 -0
- data/lib/flowcommerce_spree.rb +31 -0
- data/lib/flowcommerce_spree/api.rb +48 -0
- data/lib/flowcommerce_spree/engine.rb +27 -0
- data/lib/flowcommerce_spree/experience_service.rb +65 -0
- data/lib/flowcommerce_spree/logging_http_client.rb +43 -0
- data/lib/flowcommerce_spree/logging_http_handler.rb +15 -0
- data/lib/flowcommerce_spree/refresher.rb +81 -0
- data/lib/flowcommerce_spree/session.rb +71 -0
- data/lib/flowcommerce_spree/version.rb +5 -0
- data/lib/flowcommerce_spree/webhook_service.rb +98 -0
- data/lib/simple_csv_writer.rb +44 -0
- data/lib/tasks/flowcommerce_spree.rake +289 -0
- metadata +220 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
OrderMailer.class_eval do
|
5
|
+
# default from: ApplicationMailer::DEFAULT_FROM
|
6
|
+
|
7
|
+
def refund_complete_email(web_hook_event)
|
8
|
+
auth_id = web_hook_event.dig('refund', 'authorization', 'key')
|
9
|
+
|
10
|
+
raise Flow::Error, 'authorization key not found in WebHookEvent [refund_capture_upserted_v2]' unless auth_id
|
11
|
+
|
12
|
+
authorization = FlowcommerceSpree.client.authorizations.get_by_key FlowcommerceSpree::ORGANIZATION, auth_id
|
13
|
+
|
14
|
+
refund_requested = web_hook_event['refund']['requested']
|
15
|
+
@mail_to = authorization.customer.email
|
16
|
+
@full_name = "#{authorization.customer.name.first} #{authorization.customer.name.last}"
|
17
|
+
@amount = "#{refund_requested['amount']} #{refund_requested['currency']}"
|
18
|
+
@number = authorization.order.number
|
19
|
+
@order = Spree::Order.find_by number: @number
|
20
|
+
|
21
|
+
mail(to: @mail_to, subject: "We refunded your order for ammount #{@amount}")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Flow specific methods for Spree::Product
|
4
|
+
module Spree
|
5
|
+
module FlowIoProductDecorator
|
6
|
+
def self.prepended(base)
|
7
|
+
base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
|
8
|
+
|
9
|
+
base.store_accessor :meta, :flow_data, :zone_ids
|
10
|
+
end
|
11
|
+
|
12
|
+
def price_in_zone(currency, product_zone)
|
13
|
+
flow_experience_key = product_zone&.flow_data&.[]('key')
|
14
|
+
return flow_local_price(flow_experience_key) if flow_experience_key.present?
|
15
|
+
|
16
|
+
price_in(currency)
|
17
|
+
end
|
18
|
+
|
19
|
+
# returns price bound to local experience from master variant
|
20
|
+
def flow_local_price(flow_exp)
|
21
|
+
master.flow_local_price(flow_exp) || Spree::Price.new(variant_id: id, currency: 'USD', amount: 0)
|
22
|
+
end
|
23
|
+
|
24
|
+
def flow_included?(flow_exp)
|
25
|
+
return true unless flow_exp
|
26
|
+
|
27
|
+
flow_data["#{flow_exp.key}.excluded"].to_i != 1
|
28
|
+
end
|
29
|
+
|
30
|
+
def price_range(product_zone)
|
31
|
+
prices = {}
|
32
|
+
master_prices.each do |p|
|
33
|
+
currency = p.currency
|
34
|
+
min = nil
|
35
|
+
max = nil
|
36
|
+
|
37
|
+
if variants.any?
|
38
|
+
variants.each do |v|
|
39
|
+
price = v.price_in(currency)
|
40
|
+
next if price.nil? || price.amount.nil?
|
41
|
+
|
42
|
+
min = price if min.nil? || min.amount > price.amount
|
43
|
+
max = price if max.nil? || max.amount < price.amount
|
44
|
+
end
|
45
|
+
else
|
46
|
+
min = max = master.price_in(currency)
|
47
|
+
end
|
48
|
+
|
49
|
+
rmin = min&.amount&.to_s(:rounded, precision: 0) || 0
|
50
|
+
rmax = max&.amount&.to_s(:rounded, precision: 0) || 0
|
51
|
+
|
52
|
+
prices[currency] = rmin == rmax ? { amount: rmin } : { min: rmin, max: rmax }
|
53
|
+
end
|
54
|
+
|
55
|
+
add_flow_price_range(prices, product_zone)
|
56
|
+
end
|
57
|
+
|
58
|
+
def add_flow_price_range(prices, product_zone)
|
59
|
+
flow_experience_key = product_zone&.flow_data&.[]('key')
|
60
|
+
return prices if flow_experience_key.blank?
|
61
|
+
|
62
|
+
master_price = master.flow_local_price(flow_experience_key)
|
63
|
+
currency = product_zone.flow_io_experience_currency
|
64
|
+
min = nil
|
65
|
+
max = nil
|
66
|
+
|
67
|
+
if variants.any?
|
68
|
+
variants.each do |v|
|
69
|
+
price = v.flow_local_price(flow_experience_key)
|
70
|
+
next if price.amount.nil? || price.currency != currency
|
71
|
+
|
72
|
+
min = price if min.nil? || min.amount > price.amount
|
73
|
+
max = price if max.nil? || max.amount < price.amount
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
if master_price.currency == currency
|
78
|
+
min ||= master_price
|
79
|
+
max ||= master_price
|
80
|
+
end
|
81
|
+
|
82
|
+
rmin = min&.amount&.to_s(:rounded, precision: 0) || 0
|
83
|
+
rmax = max&.amount&.to_s(:rounded, precision: 0) || 0
|
84
|
+
|
85
|
+
prices[currency] = rmin == rmax ? { amount: rmin } : { min: rmin, max: rmax }
|
86
|
+
prices
|
87
|
+
end
|
88
|
+
|
89
|
+
Spree::Product.prepend(self) if Spree::Product.included_modules.exclude?(self)
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Flow specific methods for Spree::Variant
|
4
|
+
# Spree save all the prices inside Variant object. We choose to have a cache jsonb field named flow_data that will
|
5
|
+
# hold all important Flow sync data for specific experiences.
|
6
|
+
module Spree
|
7
|
+
module FlowIoVariantDecorator
|
8
|
+
def self.prepended(base)
|
9
|
+
base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
|
10
|
+
|
11
|
+
base.store_accessor :meta, :flow_data
|
12
|
+
|
13
|
+
# after every save we sync product we generate sh1 checksums to update only when change happend
|
14
|
+
base.after_save :sync_product_to_flow
|
15
|
+
end
|
16
|
+
|
17
|
+
def experiences
|
18
|
+
flow_data&.[]('exp')
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_flow_io_experience_data(exp, value)
|
22
|
+
raise ArgumentError, 'Value should be a hash' unless value.is_a?(Hash)
|
23
|
+
|
24
|
+
self.flow_data = flow_data || {}
|
25
|
+
self.flow_data['exp'] ||= {}
|
26
|
+
self.flow_data['exp'][exp] = value
|
27
|
+
end
|
28
|
+
|
29
|
+
# clears flow_data from the records
|
30
|
+
def truncate_flow_data
|
31
|
+
flow_data&.[]('exp')&.keys&.each do |exp_key|
|
32
|
+
break unless (product = self.product)
|
33
|
+
|
34
|
+
remove_experience_from_product(exp_key, product)
|
35
|
+
end
|
36
|
+
|
37
|
+
meta.delete(:flow_data)
|
38
|
+
update_column(:meta, meta.to_json)
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove_experience_from_product(exp_key, product)
|
42
|
+
return unless (zone = Spree::Zones::Product.find_by(name: exp_key.titleize))
|
43
|
+
|
44
|
+
zone_ids = product.zone_ids || []
|
45
|
+
zone_id_string = zone.id.to_s
|
46
|
+
return unless zone_ids.include?(zone_id_string)
|
47
|
+
|
48
|
+
product.zone_ids = zone_ids - [zone_id_string]
|
49
|
+
product.update_columns(meta: product.meta.to_json)
|
50
|
+
end
|
51
|
+
|
52
|
+
# upload product variant to Flow's Product Catalog
|
53
|
+
def sync_product_to_flow
|
54
|
+
# initial Spree seed will fail, so skip unless we have Flow data field
|
55
|
+
return unless respond_to?(:flow_data)
|
56
|
+
|
57
|
+
return if FlowcommerceSpree::API_KEY.blank? || FlowcommerceSpree::API_KEY == 'test_key'
|
58
|
+
|
59
|
+
return { error: 'Price is 0' } if price == 0
|
60
|
+
|
61
|
+
additional_attrs = {}
|
62
|
+
attr_name = nil
|
63
|
+
export_required = false
|
64
|
+
FlowcommerceSpree::Config.additional_attributes[self.class.name.tableize.tr('/', '_').to_sym]&.each do |attr_item|
|
65
|
+
attr_name = attr_item[0]
|
66
|
+
# Flow.io could require a different attribute name, as in case of Fulfil's :customs_description - it has the
|
67
|
+
# export_name `:materials` for flow.io. That's why 1st we're checking if an export_name is defined for the
|
68
|
+
# attribute.
|
69
|
+
attr_flowcommerce_name = attr_item[1][:export_name] || attr_name
|
70
|
+
export_required = attr_item[1][:export] == :required
|
71
|
+
attr_value = __send__(attr_name)
|
72
|
+
break if export_required && attr_value.blank?
|
73
|
+
|
74
|
+
additional_attrs[attr_flowcommerce_name] = attr_value if attr_value
|
75
|
+
end
|
76
|
+
|
77
|
+
if export_required && additional_attrs[attr_value].blank?
|
78
|
+
return { error: "Variant with sku = #{sku} has no #{attr_name}" }
|
79
|
+
end
|
80
|
+
|
81
|
+
flow_item = to_flowcommerce_item(additional_attrs)
|
82
|
+
flow_item_sh1 = Digest::SHA1.hexdigest(flow_item.to_json)
|
83
|
+
|
84
|
+
# skip if sync not needed
|
85
|
+
return nil if flow_data&.[](:last_sync_sh1) == flow_item_sh1
|
86
|
+
|
87
|
+
response = FlowcommerceSpree.client.items.put_by_number(FlowcommerceSpree::ORGANIZATION, sku, flow_item)
|
88
|
+
self.flow_data ||= {}
|
89
|
+
self.flow_data[:last_sync_sh1] = flow_item_sh1
|
90
|
+
|
91
|
+
# after successful put, write cache
|
92
|
+
update_column(:meta, meta.to_json)
|
93
|
+
|
94
|
+
response
|
95
|
+
rescue Net::OpenTimeout => e
|
96
|
+
{ error: e.message }
|
97
|
+
end
|
98
|
+
|
99
|
+
def flow_prices(flow_exp)
|
100
|
+
flow_data&.dig(:exp, flow_exp, :prices) || []
|
101
|
+
end
|
102
|
+
|
103
|
+
# returns price bound to local experience
|
104
|
+
def flow_local_price(flow_exp)
|
105
|
+
price_object = flow_prices(flow_exp)&.first
|
106
|
+
amount = price_object&.[](:amount) || price
|
107
|
+
currency = price_object&.[](:currency) || cost_currency
|
108
|
+
Spree::Price.new(variant_id: id, currency: currency, amount: amount)
|
109
|
+
end
|
110
|
+
|
111
|
+
def price_in_zone(currency, product_zone)
|
112
|
+
flow_experience_key = product_zone&.flow_data&.[]('key')
|
113
|
+
return flow_local_price(flow_experience_key) if flow_experience_key.present?
|
114
|
+
|
115
|
+
price_in(currency)
|
116
|
+
end
|
117
|
+
|
118
|
+
def all_prices_in_zone(product_zone)
|
119
|
+
all_prices = prices.map { |price| { currency: price.currency, amount: (price.amount&.round || 0).to_s } }
|
120
|
+
|
121
|
+
flow_experience_key = product_zone&.flow_data&.[]('key')
|
122
|
+
return all_prices if flow_experience_key.blank?
|
123
|
+
|
124
|
+
flow_price = flow_local_price(flow_experience_key)
|
125
|
+
all_prices << { currency: flow_price.currency, amount: (flow_price.amount&.round || 0).to_s }
|
126
|
+
all_prices
|
127
|
+
end
|
128
|
+
|
129
|
+
# creates object for flow api
|
130
|
+
def to_flowcommerce_item(additional_attrs)
|
131
|
+
# add product categories
|
132
|
+
categories = []
|
133
|
+
taxon = product.taxons.first
|
134
|
+
current_taxon = taxon
|
135
|
+
while current_taxon
|
136
|
+
categories.unshift current_taxon.name
|
137
|
+
current_taxon = current_taxon.parent
|
138
|
+
end
|
139
|
+
|
140
|
+
images = if (image = product.images.first || product.variant_images.first)
|
141
|
+
asset_host_scheme = ENV.fetch('ASSET_HOST_PROTOCOL', 'https')
|
142
|
+
asset_host = ENV.fetch('ASSET_HOST', 'staging.mejuri.com')
|
143
|
+
large_image_uri = URI(image.attachment(:large))
|
144
|
+
product_image_uri = URI(image.attachment.url(:product))
|
145
|
+
large_image_uri.scheme ||= asset_host_scheme
|
146
|
+
product_image_uri.scheme ||= asset_host_scheme
|
147
|
+
large_image_uri.host ||= asset_host
|
148
|
+
product_image_uri.host ||= asset_host
|
149
|
+
|
150
|
+
[{ url: large_image_uri.to_s, tags: ['checkout'] },
|
151
|
+
{ url: product_image_uri.to_s, tags: ['thumbnail'] }]
|
152
|
+
else
|
153
|
+
[]
|
154
|
+
end
|
155
|
+
|
156
|
+
Io::Flow::V0::Models::ItemForm.new(
|
157
|
+
number: sku,
|
158
|
+
locale: 'en_US',
|
159
|
+
language: 'en',
|
160
|
+
name: product.name,
|
161
|
+
description: product.description,
|
162
|
+
currency: cost_currency,
|
163
|
+
price: price.to_f,
|
164
|
+
images: images,
|
165
|
+
categories: categories,
|
166
|
+
attributes: common_attrs(taxon).merge!(additional_attrs)
|
167
|
+
)
|
168
|
+
end
|
169
|
+
|
170
|
+
def common_attrs(taxon)
|
171
|
+
{
|
172
|
+
weight: weight.to_s,
|
173
|
+
height: height.to_s,
|
174
|
+
width: width.to_s,
|
175
|
+
depth: depth.to_s,
|
176
|
+
is_master: is_master ? 'true' : 'false',
|
177
|
+
product_id: product_id.to_s,
|
178
|
+
tax_category: product.tax_category_id.to_s,
|
179
|
+
product_description: product.description,
|
180
|
+
product_shipping_category: product.shipping_category_id ? shipping_category.name : nil,
|
181
|
+
product_meta_title: taxon&.meta_title.to_s,
|
182
|
+
product_meta_description: taxon&.meta_description.to_s,
|
183
|
+
product_meta_keywords: taxon&.meta_keywords.to_s,
|
184
|
+
product_slug: product.slug
|
185
|
+
}.select { |_k, v| v.present? }
|
186
|
+
end
|
187
|
+
|
188
|
+
# gets flow catalog item, and imports it
|
189
|
+
# called from flow:sync_localized_items rake task
|
190
|
+
def flow_import_item(item_hash, experience_key: nil)
|
191
|
+
# If experience not specified, get it from the local hash of imported variant
|
192
|
+
experience_key ||= item_hash.dig(:local, :experience, :key)
|
193
|
+
current_experience_meta = item_hash.delete(:local)
|
194
|
+
|
195
|
+
# Do not repeatedly store Experience data - this is stored in Spree::Zones::Product
|
196
|
+
current_experience_meta.delete(:experience)
|
197
|
+
add_flow_io_experience_data(experience_key, current_experience_meta)
|
198
|
+
self.flow_data.merge!(item_hash)
|
199
|
+
|
200
|
+
update_column(:meta, meta.to_json)
|
201
|
+
end
|
202
|
+
|
203
|
+
Spree::Variant.prepend(self) if Spree::Variant.included_modules.exclude?(self)
|
204
|
+
end
|
205
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Flow.io (2017)
|
4
|
+
# adapter for Spree that talks to activemerchant_flow
|
5
|
+
module Spree
|
6
|
+
class Gateway
|
7
|
+
class Flow < Gateway
|
8
|
+
def provider_class
|
9
|
+
self.class
|
10
|
+
end
|
11
|
+
|
12
|
+
def actions
|
13
|
+
%w[capture authorize purchase refund void]
|
14
|
+
end
|
15
|
+
|
16
|
+
# if user wants to force auto capture
|
17
|
+
def auto_capture?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def payment_profiles_supported?
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_type
|
26
|
+
'gateway'
|
27
|
+
end
|
28
|
+
|
29
|
+
def preferences
|
30
|
+
{}
|
31
|
+
end
|
32
|
+
|
33
|
+
def supports?(source)
|
34
|
+
# flow supports credit cards
|
35
|
+
source.class == Spree::CreditCard
|
36
|
+
end
|
37
|
+
|
38
|
+
def authorize(_amount, _payment_method, options = {})
|
39
|
+
order = load_order options
|
40
|
+
order.cc_authorization
|
41
|
+
end
|
42
|
+
|
43
|
+
def capture(_amount, _payment_method, options = {})
|
44
|
+
order = load_order options
|
45
|
+
order.cc_capture
|
46
|
+
end
|
47
|
+
|
48
|
+
def purchase(_amount, _payment_method, options = {})
|
49
|
+
order = load_order options
|
50
|
+
flow_auth = order.cc_authorization
|
51
|
+
|
52
|
+
if flow_auth.success?
|
53
|
+
order.cc_capture
|
54
|
+
else
|
55
|
+
flow_auth
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def refund(_money, _authorization_key, options = {})
|
60
|
+
order = load_order options
|
61
|
+
order.cc_refund
|
62
|
+
end
|
63
|
+
|
64
|
+
def void(money, authorization_key, options = {})
|
65
|
+
# binding.pry
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_profile(payment)
|
69
|
+
# binding.pry
|
70
|
+
|
71
|
+
# payment.order.state
|
72
|
+
@credit_card = payment.source
|
73
|
+
|
74
|
+
profile_ensure_payment_method_is_present!
|
75
|
+
create_flow_cc_profile!
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
# hard inject Flow as payment method unless defined
|
81
|
+
def profile_ensure_payment_method_is_present!
|
82
|
+
return if @credit_card.payment_method_id
|
83
|
+
|
84
|
+
flow_payment = Spree::PaymentMethod.where(active: true, type: 'Spree::Gateway::Flow').first
|
85
|
+
@credit_card.payment_method_id = flow_payment.id if flow_payment
|
86
|
+
end
|
87
|
+
|
88
|
+
# create payment profile with Flow and tokenize Credit Card
|
89
|
+
def create_flow_cc_profile!
|
90
|
+
return if @credit_card.gateway_customer_profile_id
|
91
|
+
return unless @credit_card.verification_value
|
92
|
+
|
93
|
+
# build credit card hash
|
94
|
+
data = {}
|
95
|
+
data[:number] = @credit_card.number
|
96
|
+
data[:name] = @credit_card.name
|
97
|
+
data[:cvv] = @credit_card.verification_value
|
98
|
+
data[:expiration_year] = @credit_card.year.to_i
|
99
|
+
data[:expiration_month] = @credit_card.month.to_i
|
100
|
+
|
101
|
+
# tokenize with Flow
|
102
|
+
# rescue Io::Flow::V0::HttpClient::ServerError
|
103
|
+
card_form = ::Io::Flow::V0::Models::CardForm.new(data)
|
104
|
+
result = FlowcommerceSpree.client.cards.post(::FlowcommerceSpree::ORGANIZATION, card_form)
|
105
|
+
|
106
|
+
@credit_card.update_column :gateway_customer_profile_id, result.token
|
107
|
+
end
|
108
|
+
|
109
|
+
def load_order(options)
|
110
|
+
order_number = options[:order_id].split('-').first
|
111
|
+
spree_order = Spree::Order.find_by number: order_number
|
112
|
+
::Flow::SimpleGateway.new spree_order
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|