flowcommerce-solidus 0.1.11
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/.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
|
+
|