flowcommerce-solidus 0.1.11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.version +1 -0
- data/bin/flowcommerce-solidus +121 -0
- data/lib/flowcommerce-solidus.rb +7 -0
- data/static/app/flow/README.md +77 -0
- data/static/app/flow/SOLIDUS_FLOW.md +127 -0
- data/static/app/flow/decorators/admin_decorators.rb +34 -0
- data/static/app/flow/decorators/localized_coupon_code_decorator.rb +49 -0
- data/static/app/flow/decorators/spree_credit_card_decorator.rb +35 -0
- data/static/app/flow/decorators/spree_order_decorator.rb +128 -0
- data/static/app/flow/decorators/spree_product_decorator.rb +16 -0
- data/static/app/flow/decorators/spree_user_decorator.rb +16 -0
- data/static/app/flow/decorators/spree_variant_decorator.rb +124 -0
- data/static/app/flow/flow.rb +67 -0
- data/static/app/flow/flow/error.rb +46 -0
- data/static/app/flow/flow/experience.rb +51 -0
- data/static/app/flow/flow/order.rb +267 -0
- data/static/app/flow/flow/pay_pal.rb +27 -0
- data/static/app/flow/flow/session.rb +77 -0
- data/static/app/flow/flow/simple_crypt.rb +30 -0
- data/static/app/flow/flow/simple_gateway.rb +123 -0
- data/static/app/flow/flow/webhook.rb +62 -0
- data/static/app/flow/lib/flow_api_refresh.rb +89 -0
- data/static/app/flow/lib/spree_flow_gateway.rb +86 -0
- data/static/app/flow/lib/spree_stripe_gateway.rb +142 -0
- data/static/app/views/spree/admin/payments/index.html.erb +37 -0
- data/static/app/views/spree/admin/promotions/edit.html.erb +59 -0
- data/static/app/views/spree/admin/shared/_order_summary.html.erb +58 -0
- data/static/app/views/spree/admin/shared/_order_summary_flow.html.erb +13 -0
- data/static/app/views/spree/order_mailer/confirm_email.html.erb +85 -0
- data/static/app/views/spree/order_mailer/confirm_email.text.erb +41 -0
- data/static/lib/tasks/flow.rake +248 -0
- metadata +160 -0
@@ -0,0 +1,267 @@
|
|
1
|
+
# represents flow.io order
|
2
|
+
# for easy intgration we pass current
|
3
|
+
# - flow experirnce
|
4
|
+
# - solidus / spree order
|
5
|
+
# - current customer, presetnt as @current_spree_user controller instance variable
|
6
|
+
#
|
7
|
+
# example:
|
8
|
+
# flow_order = Flow::Order.new # init flow-order object
|
9
|
+
# order: Spree::Order.last,
|
10
|
+
# experience: @flow_session.experience
|
11
|
+
# customer: Spree::User.last
|
12
|
+
# fo.build_flow_request # builds json body to be posted to flow api
|
13
|
+
# fo.synchronize! # sends order to flow
|
14
|
+
|
15
|
+
class Flow::Order
|
16
|
+
FLOW_CENTER = 'default' unless defined?(::Flow::Order::FLOW_CENTER)
|
17
|
+
|
18
|
+
attr_reader :response
|
19
|
+
attr_reader :order
|
20
|
+
attr_reader :customer
|
21
|
+
attr_reader :body
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def clear_cache order
|
25
|
+
return unless order.flow_data['order']
|
26
|
+
order.flow_data.delete('order')
|
27
|
+
order.update_column :flow_data, order.flow_data.dup
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
###
|
32
|
+
|
33
|
+
def initialize order:, experience: nil, customer: nil
|
34
|
+
# when sending email, we do not have experience defined
|
35
|
+
unless experience
|
36
|
+
if order.flow_order
|
37
|
+
experience = Flow::Experience.get(order.flow_order['experience']['key'])
|
38
|
+
else
|
39
|
+
raise(ArgumentError, 'Experience not defined and not found in flow data')
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
@experience = experience
|
44
|
+
@order = order
|
45
|
+
@customer = customer
|
46
|
+
@items = []
|
47
|
+
end
|
48
|
+
|
49
|
+
# helper method to send complete order from spreee to flow
|
50
|
+
def synchronize!
|
51
|
+
sync_body!
|
52
|
+
check_state!
|
53
|
+
write_response_in_cache
|
54
|
+
@response
|
55
|
+
end
|
56
|
+
|
57
|
+
def error
|
58
|
+
@response['messages'].join(', ')
|
59
|
+
end
|
60
|
+
|
61
|
+
def error_code
|
62
|
+
@response['code']
|
63
|
+
end
|
64
|
+
|
65
|
+
def error?
|
66
|
+
@response && @response['code'] && @response['messages']
|
67
|
+
end
|
68
|
+
|
69
|
+
def delivery
|
70
|
+
deliveries.select{ |el| el[:active] }.first
|
71
|
+
end
|
72
|
+
|
73
|
+
# delivery methods are defined in flow console
|
74
|
+
def deliveries
|
75
|
+
# if we have erorr with an order, but still using this method
|
76
|
+
return [] unless @order.flow_order
|
77
|
+
|
78
|
+
@order.flow_data ||= {}
|
79
|
+
|
80
|
+
delivery_list = @order.flow_order['deliveries'][0]['options']
|
81
|
+
delivery_list = delivery_list.map do |opts|
|
82
|
+
name = opts['tier']['name']
|
83
|
+
name += ' (%s)' % opts['tier']['strategy'] if opts['tier']['strategy']
|
84
|
+
selection_id = opts['id']
|
85
|
+
|
86
|
+
{
|
87
|
+
id: selection_id,
|
88
|
+
price: { label: opts['price']['label'] },
|
89
|
+
active: @order.flow_order['selections'].include?(selection_id),
|
90
|
+
name: name
|
91
|
+
}
|
92
|
+
end.to_a
|
93
|
+
|
94
|
+
# make first one active unless we have active element
|
95
|
+
delivery_list.first[:active] = true unless delivery_list.select{ |el| el[:active] }.first
|
96
|
+
|
97
|
+
delivery_list
|
98
|
+
end
|
99
|
+
|
100
|
+
def total_price
|
101
|
+
@order.flow_total
|
102
|
+
end
|
103
|
+
|
104
|
+
def delivered_duty
|
105
|
+
# paid is default
|
106
|
+
@order.flow_data['delivered_duty'] || ::Io::Flow::V0::Models::DeliveredDuty.paid.value
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# if customer is defined, add customer info
|
112
|
+
# it is possible to have order in solidus without customer info (new guest session)
|
113
|
+
def add_customer opts
|
114
|
+
return unless @customer
|
115
|
+
|
116
|
+
address = @customer.ship_address
|
117
|
+
# address = nil
|
118
|
+
if address
|
119
|
+
opts[:customer] = {
|
120
|
+
name: {
|
121
|
+
first: address.firstname,
|
122
|
+
last: address.lastname
|
123
|
+
},
|
124
|
+
email: @customer.email,
|
125
|
+
number: @customer.flow_number,
|
126
|
+
phone: address.phone
|
127
|
+
}
|
128
|
+
|
129
|
+
streets = []
|
130
|
+
streets.push address.address1 unless address.address1.blank?
|
131
|
+
streets.push address.address2 unless address.address2.blank?
|
132
|
+
|
133
|
+
opts[:destination] = {
|
134
|
+
streets: streets,
|
135
|
+
city: address.city,
|
136
|
+
province: address.state_name,
|
137
|
+
postal: address.zipcode,
|
138
|
+
country: (address.country.iso3 rescue 'USA'),
|
139
|
+
contact: opts[:customer]
|
140
|
+
}
|
141
|
+
|
142
|
+
opts[:destination].delete_if { |k,v| v.nil? }
|
143
|
+
end
|
144
|
+
|
145
|
+
opts
|
146
|
+
end
|
147
|
+
|
148
|
+
# builds object that can be sent to api.flow.io to sync order data
|
149
|
+
def build_flow_request
|
150
|
+
@order.line_items.each do |line_item|
|
151
|
+
add_item line_item
|
152
|
+
end
|
153
|
+
|
154
|
+
flow_number = @order.flow_number
|
155
|
+
|
156
|
+
opts = {}
|
157
|
+
opts[:organization] = Flow.organization
|
158
|
+
opts[:experience] = @experience.key
|
159
|
+
opts[:expand] = 'experience'
|
160
|
+
|
161
|
+
body = {}
|
162
|
+
body = {
|
163
|
+
items: @items,
|
164
|
+
number: flow_number
|
165
|
+
}
|
166
|
+
|
167
|
+
add_customer body if @customer
|
168
|
+
|
169
|
+
# if defined, add selection (delivery options) and delivered_duty from flow_data
|
170
|
+
body[:selections] = [@order.flow_data['selection']] if @order.flow_data['selection']
|
171
|
+
body[:delivered_duty] = @order.flow_data['delivered_duty'] if @order.flow_data['delivered_duty']
|
172
|
+
|
173
|
+
# discount on full order is applied
|
174
|
+
if @order.adjustment_total != 0
|
175
|
+
body[:discount] = {
|
176
|
+
amount: @order.adjustment_total,
|
177
|
+
currency: @order.currency
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
# calculate digest body and cache it
|
182
|
+
@digest = Digest::SHA1.hexdigest(opts.to_json + body.to_json)
|
183
|
+
|
184
|
+
[opts, body]
|
185
|
+
end
|
186
|
+
|
187
|
+
def sync_body!
|
188
|
+
opts, @body = build_flow_request
|
189
|
+
|
190
|
+
@use_get = false
|
191
|
+
|
192
|
+
# use get if order is completed and closed
|
193
|
+
@use_get = true if @order.state == 'complete'
|
194
|
+
|
195
|
+
# use get if local digest hash check said there is no change
|
196
|
+
@use_get ||= true if @order.flow_data['digest'] == @digest
|
197
|
+
|
198
|
+
# do not use get if there is no local order cache
|
199
|
+
@use_get = false unless @order.flow_data['order']
|
200
|
+
|
201
|
+
if @use_get
|
202
|
+
@response = Flow.api :get, '/:organization/orders/%s' % @body[:number], expand: 'experience'
|
203
|
+
else
|
204
|
+
# replace when fixed integer error
|
205
|
+
# @body[:items].map! { |item| ::Io::Flow::V0::Models::LineItemForm.new(item) }
|
206
|
+
# opts[:experience] = @experience.key
|
207
|
+
# order_put_form = ::Io::Flow::V0::Models::OrderPutForm.new(@body)
|
208
|
+
# r FlowCommerce.instance.orders.put_by_number(Flow.organization, @order.flow_number, order_put_form, opts)
|
209
|
+
|
210
|
+
@response = Flow.api :put, '/:organization/orders/%s' % @body[:number], opts, @body
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def check_state!
|
215
|
+
# authorize if not authorized
|
216
|
+
# if !@order.flow_order_authorized?
|
217
|
+
|
218
|
+
# authorize payment on complete, unless authorized
|
219
|
+
if @order.state == 'complete' && !@order.flow_order_authorized?
|
220
|
+
simple_gateway = Flow::SimpleGateway.new(@order)
|
221
|
+
simple_gateway.cc_authorization
|
222
|
+
end
|
223
|
+
|
224
|
+
@order.flow_finalize! if @order.flow_order_authorized? && @order.state != 'complete'
|
225
|
+
end
|
226
|
+
|
227
|
+
def add_item line_item
|
228
|
+
variant = line_item.variant
|
229
|
+
price_root = variant.flow_data['exp'][@experience.key]['prices'][0] rescue {}
|
230
|
+
|
231
|
+
# create flow order line item
|
232
|
+
item = {
|
233
|
+
center: FLOW_CENTER,
|
234
|
+
number: variant.id.to_s,
|
235
|
+
quantity: line_item.quantity,
|
236
|
+
price: {
|
237
|
+
amount: price_root['amount'] || variant.cost_price,
|
238
|
+
currency: price_root['currency'] || variant.cost_currency
|
239
|
+
}
|
240
|
+
}
|
241
|
+
|
242
|
+
@items.push item
|
243
|
+
end
|
244
|
+
|
245
|
+
# set cache for total order ammount
|
246
|
+
# written in flow_data field inside spree_orders table
|
247
|
+
def write_response_in_cache
|
248
|
+
if !@response || error?
|
249
|
+
@order.flow_data.delete('digest')
|
250
|
+
@order.flow_data.delete('order')
|
251
|
+
else
|
252
|
+
response_total = @response.dig('total', 'label')
|
253
|
+
cache_total = @order.flow_data.dig('order', 'total', 'label')
|
254
|
+
|
255
|
+
# return if total is not changed, no products removed or added
|
256
|
+
return if @use_get && response_total == cache_total
|
257
|
+
|
258
|
+
# update local order
|
259
|
+
@order.flow_data['digest'] = @digest
|
260
|
+
@order.flow_data['order'] = @response.to_hash
|
261
|
+
end
|
262
|
+
|
263
|
+
@order.save
|
264
|
+
end
|
265
|
+
|
266
|
+
end
|
267
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Flow.io (2017)
|
2
|
+
# communicates with flow api, responds to webhook events
|
3
|
+
|
4
|
+
module Flow::PayPal
|
5
|
+
extend self
|
6
|
+
|
7
|
+
def get_id(order)
|
8
|
+
if order.flow_order
|
9
|
+
# get PayPal ID using Flow api
|
10
|
+
body = {
|
11
|
+
# discriminator: 'merchant_of_record_payment_form',
|
12
|
+
method: 'paypal',
|
13
|
+
order_number: order.number,
|
14
|
+
amount: order.flow_order.total.amount,
|
15
|
+
currency: order.flow_order.total.currency,
|
16
|
+
}
|
17
|
+
|
18
|
+
# Flow.api :post, '/:organization/payments', {}, body
|
19
|
+
|
20
|
+
form = ::Io::Flow::V0::Models::MerchantOfRecordPaymentForm.new body
|
21
|
+
FlowCommerce.instance.payments.post Flow.organization, form
|
22
|
+
else
|
23
|
+
# to do
|
24
|
+
raise 'PayPal only supported while using flow'
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Flow.io (2017)
|
2
|
+
# communicates with flow api, easy access to session
|
3
|
+
|
4
|
+
class Flow::Session
|
5
|
+
attr_accessor :session, :localized, :visitor
|
6
|
+
|
7
|
+
def self.restore packed_session
|
8
|
+
Marshal.load packed_session
|
9
|
+
end
|
10
|
+
|
11
|
+
# flow session can ve created via IP or local cached OrganizationSession dump
|
12
|
+
# Flow::Experience.all.first.key
|
13
|
+
# Flow sessions need buest-guess visitor_id and
|
14
|
+
def initialize ip:, visitor:
|
15
|
+
ip = '127.0.0.1' if ip == '::1'
|
16
|
+
|
17
|
+
@ip = ip
|
18
|
+
@visitor = visitor
|
19
|
+
end
|
20
|
+
|
21
|
+
# create session with blank data
|
22
|
+
def create
|
23
|
+
data = {
|
24
|
+
ip: @ip,
|
25
|
+
visit: {
|
26
|
+
id: @visitor,
|
27
|
+
expires_at: (Time.now+30.minutes).iso8601
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
session_model = ::Io::Flow::V0::Models::SessionForm.new data
|
32
|
+
@session = FlowCommerce.instance.sessions.post_organizations_by_organization Flow.organization, session_model
|
33
|
+
end
|
34
|
+
|
35
|
+
# if we want to manualy switch to specific country or experience
|
36
|
+
def update data
|
37
|
+
@session = FlowCommerce.instance.sessions.put_by_session(
|
38
|
+
@session.id,
|
39
|
+
::Io::Flow::V0::Models::SessionPutForm.new(data)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def dump
|
44
|
+
Marshal.dump self
|
45
|
+
end
|
46
|
+
|
47
|
+
# get local experience or return nil
|
48
|
+
def experience
|
49
|
+
@session.local ? @session.local.experience : Flow::Experience.default
|
50
|
+
end
|
51
|
+
|
52
|
+
def local
|
53
|
+
@session.local
|
54
|
+
end
|
55
|
+
|
56
|
+
def id
|
57
|
+
@session.id
|
58
|
+
end
|
59
|
+
|
60
|
+
def localized?
|
61
|
+
# use flow if we are not in default country
|
62
|
+
return false unless local
|
63
|
+
return false if @localized.class == FalseClass
|
64
|
+
local.country.iso_3166_3 != ENV.fetch('FLOW_BASE_COUNTRY').upcase
|
65
|
+
end
|
66
|
+
|
67
|
+
# because we do not get full experience from session, we have to get from exp list
|
68
|
+
def delivered_duty_options
|
69
|
+
Hashie::Mash.new Flow::Experience.get(experience.key).settings.delivered_duty.to_hash
|
70
|
+
end
|
71
|
+
|
72
|
+
# if we have more than one choice, we show choice popup
|
73
|
+
def offers_delivered_duty_choice?
|
74
|
+
delivered_duty_options.available.length > 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Flow.io (2017)
|
2
|
+
# Module uses rails engine for encrypt and decrypt
|
3
|
+
|
4
|
+
# example
|
5
|
+
# enc1 = Flow::SimpleCrypt.encrypt('foo')
|
6
|
+
# Flow::SimpleCrypt.encrypt(enc1)
|
7
|
+
#
|
8
|
+
# example with salt
|
9
|
+
# enc2 = Flow::SimpleCrypt.encrypt('bar', '127.0.0.1')
|
10
|
+
# Flow::SimpleCrypt.encrypt(enc2) # raises error: ActiveSupport::MessageVerifier::InvalidSignature
|
11
|
+
# Flow::SimpleCrypt.encrypt(enc2, '127.0.0.1') # ok
|
12
|
+
|
13
|
+
module Flow::SimpleCrypt
|
14
|
+
extend self
|
15
|
+
|
16
|
+
def encrypt_base(salt)
|
17
|
+
local_secret = Rails.application.secrets.secret_key_base[0,32]
|
18
|
+
key = ActiveSupport::KeyGenerator.new(local_secret).generate_key(salt || '', 32)
|
19
|
+
|
20
|
+
ActiveSupport::MessageEncryptor.new(key)
|
21
|
+
end
|
22
|
+
|
23
|
+
def encrypt(raw_data, salt=nil)
|
24
|
+
encrypt_base(salt).encrypt_and_sign(raw_data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def decrypt(enc_data, salt=nil)
|
28
|
+
encrypt_base(salt).decrypt_and_verify(enc_data)
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Flow.io (2017)
|
2
|
+
# communicates with Flow payments API, easy access to session
|
3
|
+
# to basic shop frontend and backend needs
|
4
|
+
|
5
|
+
class Flow::SimpleGateway
|
6
|
+
cattr_accessor :clear_zero_amount_payments
|
7
|
+
|
8
|
+
def initialize(order)
|
9
|
+
@order = order
|
10
|
+
end
|
11
|
+
|
12
|
+
# authorises credit card and prepares for capture
|
13
|
+
def cc_authorization
|
14
|
+
auth_form = get_authorization_form
|
15
|
+
response = FlowCommerce.instance.authorizations.post(Flow.organization, auth_form)
|
16
|
+
status_message = response.result.status.value
|
17
|
+
status = status_message == ::Io::Flow::V0::Models::AuthorizationStatus.authorized.value
|
18
|
+
|
19
|
+
store = {
|
20
|
+
key: response.key,
|
21
|
+
amount: response.amount,
|
22
|
+
currency: response.currency,
|
23
|
+
authorization_id: response.id
|
24
|
+
}
|
25
|
+
|
26
|
+
@order.update_column :flow_data, @order.flow_data.merge('authorization': store)
|
27
|
+
|
28
|
+
if self.class.clear_zero_amount_payments
|
29
|
+
@order.payments.where(amount:0, state: ['invalid', 'processing', 'pending']).map(&:destroy)
|
30
|
+
end
|
31
|
+
|
32
|
+
ActiveMerchant::Billing::Response.new(status, status_message, { response: response }, { authorization: store })
|
33
|
+
rescue Io::Flow::V0::HttpClient::ServerError => exception
|
34
|
+
error_response(exception)
|
35
|
+
end
|
36
|
+
|
37
|
+
# capture authorised funds
|
38
|
+
def cc_capture
|
39
|
+
# GET /:organization/authorizations, order_number: abc
|
40
|
+
data = @order.flow_data['authorization']
|
41
|
+
|
42
|
+
raise ArgumentError, 'No Authorization data, please authorize first' unless data
|
43
|
+
|
44
|
+
capture_form = ::Io::Flow::V0::Models::CaptureForm.new(data)
|
45
|
+
response = FlowCommerce.instance.captures.post(Flow.organization, capture_form)
|
46
|
+
|
47
|
+
if response.id
|
48
|
+
@order.update_column :flow_data, @order.flow_data.merge('capture': response.to_hash)
|
49
|
+
@order.flow_finalize!
|
50
|
+
|
51
|
+
ActiveMerchant::Billing::Response.new true, 'success', { response: response }
|
52
|
+
else
|
53
|
+
ActiveMerchant::Billing::Response.new false, 'error', { response: response }
|
54
|
+
end
|
55
|
+
rescue => exception
|
56
|
+
error_response(exception)
|
57
|
+
end
|
58
|
+
|
59
|
+
def cc_refund
|
60
|
+
raise ArgumentError, 'capture info is not available' unless @order.flow_data['capture']
|
61
|
+
|
62
|
+
# we allways have capture ID, so we use it
|
63
|
+
refund_data = { capture_id: @order.flow_data['capture']['id'] }
|
64
|
+
refund_form = ::Io::Flow::V0::Models::RefundForm.new(refund_data)
|
65
|
+
response = FlowCommerce.instance.refunds.post(Flow.organization, refund_form)
|
66
|
+
|
67
|
+
if response.id
|
68
|
+
@order.update_column :flow_data, @order.flow_data.merge('refund': response.to_hash)
|
69
|
+
ActiveMerchant::Billing::Response.new true, 'success', { response: response }
|
70
|
+
else
|
71
|
+
ActiveMerchant::Billing::Response.new false, 'error', { response: response }
|
72
|
+
end
|
73
|
+
rescue => exception
|
74
|
+
error_response(exception)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# if order is not in flow, we use local solidus settings
|
80
|
+
def in_flow?
|
81
|
+
@order.flow_order ? true : false
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_authorization_form
|
85
|
+
if in_flow?
|
86
|
+
# we have order id so we allways use MerchantOfRecordAuthorizationForm
|
87
|
+
::Io::Flow::V0::Models::MerchantOfRecordAuthorizationForm.new({
|
88
|
+
'order_number': @order.flow_number,
|
89
|
+
'currency': @order.flow_order.total.currency,
|
90
|
+
'amount': @order.flow_order.total.amount,
|
91
|
+
'token': cc_get_token,
|
92
|
+
})
|
93
|
+
else
|
94
|
+
# when not using flow, we fall back to solidus default
|
95
|
+
::Io::Flow::V0::Models::DirectAuthorizationForm.new({
|
96
|
+
'currency': @order.currency,
|
97
|
+
'amount': @order.total,
|
98
|
+
'token': cc_get_token,
|
99
|
+
})
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# gets credit card token
|
104
|
+
def cc_get_token
|
105
|
+
cards = @order.credit_cards.select{ |cc| cc[:flow_data]['cc_token'] }
|
106
|
+
raise StandarError.new('Credit card not found') unless cards.first
|
107
|
+
|
108
|
+
cards.first.flow_data['cc_token'] || raise(StandardError.new 'Flow credit card token not found')
|
109
|
+
end
|
110
|
+
|
111
|
+
# we want to return errors in standardized format
|
112
|
+
def error_response(exception_object, message=nil)
|
113
|
+
message = if exception_object.respond_to?(:body) && exception_object.body.length > 0
|
114
|
+
description = JSON.load(exception_object.body)['messages'].to_sentence
|
115
|
+
'%s: %s (%s)' % [exception_object.details, description, exception_object.code]
|
116
|
+
else
|
117
|
+
exception_object.message
|
118
|
+
end
|
119
|
+
|
120
|
+
ActiveMerchant::Billing::Response.new(false, message, exception: exception_object)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|