flowcommerce_spree 0.0.3 → 0.0.4
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 +4 -4
- data/app/controllers/concerns/current_zone_loader_decorator.rb +5 -6
- data/app/controllers/flowcommerce_spree/inventory_controller.rb +23 -0
- data/app/controllers/flowcommerce_spree/orders_controller.rb +18 -0
- data/app/controllers/users/sessions_controller_decorator.rb +19 -2
- data/app/helpers/spree/core/controller_helpers/flow_io_order_helper_decorator.rb +0 -16
- data/app/models/spree/address_decorator.rb +1 -1
- data/app/models/spree/calculator/flow_io.rb +1 -1
- data/app/models/spree/flow_io_credit_card_decorator.rb +21 -0
- data/app/models/spree/{order_decorator.rb → flow_io_order_decorator.rb} +31 -65
- data/app/models/spree/gateway/flow_io.rb +61 -24
- data/app/models/spree/{credit_card_decorator.rb → payment_capture_event_decorator.rb} +1 -1
- data/app/serializers/api/v2/order_serializer_decorator.rb +20 -0
- data/app/services/flowcommerce_spree/import_experience_items.rb +1 -1
- data/app/services/flowcommerce_spree/order_sync.rb +26 -155
- data/app/services/flowcommerce_spree/order_updater.rb +76 -0
- data/app/views/spree/admin/payments/source_views/_flow_io_gateway.html.erb +21 -0
- data/config/routes.rb +2 -0
- data/db/migrate/20201021755957_add_meta_to_spree_tables.rb +6 -4
- data/lib/flow/simple_gateway.rb +0 -36
- data/lib/flowcommerce_spree.rb +3 -1
- data/lib/flowcommerce_spree/engine.rb +1 -1
- data/lib/flowcommerce_spree/logging_http_client.rb +29 -13
- data/lib/flowcommerce_spree/session.rb +0 -18
- data/lib/flowcommerce_spree/version.rb +1 -1
- data/lib/flowcommerce_spree/webhook_service.rb +74 -104
- metadata +10 -19
- data/app/models/spree/line_item_decorator.rb +0 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3810faadd82b0d21c0bacaeaa6be61bfe314ddffe4f32a6805c05554c592a745
|
4
|
+
data.tar.gz: 6d042ed69b193082492ac992ea3d4a9bd962e493ae49026a43c297492013b4ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bd6d8e87e8f5d8c5d30d9f4366bc0e6d06dc95504d6f8cd3fc8916c752478123e80671ac5da4063a02c6051eb56d95f14ef97d782249a7fa9cb1df4e983829f
|
7
|
+
data.tar.gz: 260d3b14add605bc114bd561a39f7376a88850d4aea95a98339db5461a9845463a87da1cd845cf68a6b8732a88d7f445a8961fb4f5a0847c282130efeb247083
|
@@ -20,12 +20,11 @@ CurrentZoneLoader.module_eval do
|
|
20
20
|
session['region'] = { name: current_zone_name, available_currencies: @current_zone.available_currencies,
|
21
21
|
request_iso_code: request_iso_code }
|
22
22
|
|
23
|
-
RequestStore.store[:session] = session
|
24
23
|
Rails.logger.debug("Using product zone: #{current_zone_name}")
|
25
24
|
@current_zone
|
26
25
|
end
|
27
26
|
|
28
|
-
def flow_zone
|
27
|
+
def flow_zone
|
29
28
|
return unless Spree::Zones::Product.active
|
30
29
|
.where("meta -> 'flow_data' ->> 'country' = ?",
|
31
30
|
ISO3166::Country[request_iso_code]&.alpha3).exists?
|
@@ -36,13 +35,13 @@ CurrentZoneLoader.module_eval do
|
|
36
35
|
Spree::Config[:debug_request_ip_address] || request.ip
|
37
36
|
# Germany ip: 85.214.132.117, Sweden ip: 62.20.0.196, Moldova ip: 89.41.76.29
|
38
37
|
end
|
38
|
+
|
39
|
+
# This will issue a session creation request to flow.io. The response will contain the Flow Experience key and
|
40
|
+
# the session_id
|
39
41
|
flow_io_session = FlowcommerceSpree::Session.create(ip: request_ip, visitor: visitor_id_for_flow_io)
|
40
|
-
# :create method will issue a request to flow.io. The experience, contained in the
|
41
|
-
# response, will be available in the session object - flow_io_session.experience
|
42
42
|
|
43
43
|
if (zone = Spree::Zones::Product.active.find_by(name: flow_io_session.experience&.key&.titleize))
|
44
|
-
session['
|
45
|
-
session['_f60_expires_at'] = flow_io_session.expires_at.to_s
|
44
|
+
session['flow_session_id'] = flow_io_session.id
|
46
45
|
end
|
47
46
|
|
48
47
|
zone
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowcommerceSpree
|
4
|
+
class InventoryController < ActionController::Base
|
5
|
+
def online_stock_availability
|
6
|
+
items = params['items'] || []
|
7
|
+
response = items.inject([]) { |result, item| result << check_stock(item[:id], item[:qty].to_i) }
|
8
|
+
render json: { items: response }, status: :ok
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def check_stock(flow_number, quantity)
|
14
|
+
variant = Spree::Variant.find_by(sku: flow_number)
|
15
|
+
return { id: flow_number, has_inventory: false } unless variant
|
16
|
+
|
17
|
+
{ id: flow_number, has_inventory: variant.available_online?(quantity) }
|
18
|
+
rescue StandardError
|
19
|
+
Rails.logger.error "[!] FlowcommerceSpree::InventoryController#stock unexpected Error: #{$ERROR_INFO}"
|
20
|
+
{ id: flow_number, has_inventory: false }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowcommerceSpree
|
4
|
+
class OrdersController < ApplicationController
|
5
|
+
wrap_parameters false
|
6
|
+
|
7
|
+
skip_before_action :setup_tracking, only: :order_completed
|
8
|
+
|
9
|
+
# proxy enpoint between flow and thankyou page.
|
10
|
+
# /flow/order_completed endpoint
|
11
|
+
def order_completed
|
12
|
+
flow_updater = FlowcommerceSpree::OrderUpdater.new(order: current_order)
|
13
|
+
flow_updater.complete_checkout
|
14
|
+
|
15
|
+
redirect_to "/thankyou?order=#{params[:order]}&t=#{params[:t]}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -2,10 +2,27 @@
|
|
2
2
|
|
3
3
|
module Users
|
4
4
|
SessionsController.class_eval do
|
5
|
+
# This endpoint is for returning to the FrontEnd the dynamic url to an external checkout, a flow.io url.
|
6
|
+
def checkout_url
|
7
|
+
flow_session_id = request.headers['flow-session-id']
|
8
|
+
return render json: { error: :session_id_missing }, status: 422 if flow_session_id.blank?
|
9
|
+
|
10
|
+
checkout_token =
|
11
|
+
FlowcommerceSpree::OrderSync.new(order: current_order, flow_session_id: flow_session_id).synchronize!
|
12
|
+
return render json: { error: :checkout_token_missing }, status: 422 if checkout_token.blank?
|
13
|
+
|
14
|
+
render json: { checkout_url: "https://checkout.flow.io/tokens/#{checkout_token}" }, status: 200
|
15
|
+
end
|
16
|
+
|
5
17
|
private
|
6
18
|
|
7
|
-
def
|
8
|
-
|
19
|
+
def add_optional_attrs(session_current)
|
20
|
+
session_current['user'] = current_user_attrs if current_user&.spree_api_key?
|
21
|
+
session_current['region'] = zone_attrs
|
22
|
+
|
23
|
+
external_checkout = current_zone.flow_io_active_experience?
|
24
|
+
session_current['external_checkout'] = external_checkout
|
25
|
+
session_current['flow_session_id'] = session['flow_session_id'] if external_checkout
|
9
26
|
end
|
10
27
|
end
|
11
28
|
end
|
@@ -17,22 +17,6 @@ module Spree
|
|
17
17
|
@current_order.flow_io_experience_from_zone
|
18
18
|
update_meta ||= true
|
19
19
|
end
|
20
|
-
order_flow_session_id = @current_order.flow_data['session_id']
|
21
|
-
order_session_expired = @current_order.flow_data['session_expires_at']
|
22
|
-
flow_io_session_id = session['_f60_session']
|
23
|
-
flow_io_session_expires = session['_f60_expires_at']
|
24
|
-
if flow_io_session_id.present?
|
25
|
-
if order_flow_session_id != flow_io_session_id &&
|
26
|
-
order_session_expired&.to_datetime.to_i < flow_io_session_expires&.to_datetime.to_i
|
27
|
-
@current_order.flow_data['session_id'] = flow_io_session_id
|
28
|
-
@current_order.flow_data['session_expires_at'] = flow_io_session_expires
|
29
|
-
@current_order.flow_data['checkout_token'] = nil
|
30
|
-
update_meta ||= true
|
31
|
-
end
|
32
|
-
elsif order_flow_session_id.present?
|
33
|
-
session['_f60_session'] = order_flow_session_id
|
34
|
-
session['_f60_expires_at'] = order_session_expired
|
35
|
-
end
|
36
20
|
end
|
37
21
|
|
38
22
|
if @current_order.new_record?
|
@@ -11,7 +11,7 @@ module Spree
|
|
11
11
|
address2: address_data['streets'][1],
|
12
12
|
zipcode: address_data['postal'],
|
13
13
|
city: address_data['city'],
|
14
|
-
state_name: address_data['province']
|
14
|
+
state_name: address_data['province'],
|
15
15
|
country: Spree::Country.find_by(iso3: address_data['country'])
|
16
16
|
}
|
17
17
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
module FlowIoCreditCardDecorator
|
5
|
+
def self.prepended(base)
|
6
|
+
base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
|
7
|
+
|
8
|
+
base.store_accessor :meta, :flow_data
|
9
|
+
end
|
10
|
+
|
11
|
+
def push_authorization(auth_hash)
|
12
|
+
self.flow_data ||= {}
|
13
|
+
flow_data['authorizations'] ||= []
|
14
|
+
card_authorizations = flow_data['authorizations']
|
15
|
+
card_authorizations.delete_if { |ca| ca['id'] == auth_hash['id'] }
|
16
|
+
card_authorizations << auth_hash
|
17
|
+
end
|
18
|
+
|
19
|
+
Spree::CreditCard.prepend(self) if Spree::CreditCard.included_modules.exclude?(self)
|
20
|
+
end
|
21
|
+
end
|
@@ -1,44 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
module Spree # rubocop:disable Metrics/ModuleLength
|
3
|
+
module Spree
|
5
4
|
# Added flow specific methods to Spree::Order
|
6
|
-
|
7
|
-
|
5
|
+
module FlowIoOrderDecorator
|
6
|
+
def self.included(base)
|
7
|
+
base.serialize :meta, ActiveRecord::Coders::JSON.new(symbolize_keys: true)
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
before_save :sync_to_flow_io
|
12
|
-
after_touch :sync_to_flow_io
|
9
|
+
base.store_accessor :meta, :flow_data
|
10
|
+
end
|
13
11
|
|
14
12
|
def flow_tax_cache_key
|
15
13
|
[number, 'flowcommerce', 'allocation', line_items.sum(:quantity)].join('-')
|
16
14
|
end
|
17
15
|
|
18
|
-
def sync_to_flow_io
|
19
|
-
return unless zone&.flow_io_active_experience? && state == 'cart' && line_items.size > 0
|
20
|
-
|
21
|
-
flow_io_order = FlowcommerceSpree::OrderSync.new(order: self)
|
22
|
-
flow_io_order.build_flow_request
|
23
|
-
flow_io_order.synchronize! if flow_data['digest'] != flow_io_order.digest
|
24
|
-
end
|
25
|
-
|
26
16
|
def display_total
|
27
|
-
|
28
|
-
|
29
|
-
|
17
|
+
return unless flow_data&.[]('order')
|
18
|
+
|
19
|
+
Spree::Money.new(flow_io_total_amount, currency: currency)
|
30
20
|
end
|
31
21
|
|
32
22
|
def flow_order
|
33
|
-
|
34
|
-
|
35
|
-
Hashie::Mash.new flow_data['order']
|
23
|
+
flow_data&.[]('order')
|
36
24
|
end
|
37
25
|
|
38
26
|
# accepts line item, usually called from views
|
39
27
|
def flow_line_item_price(line_item, total = false)
|
40
|
-
result = if flow_order
|
41
|
-
item =
|
28
|
+
result = if (order = flow_order)
|
29
|
+
item = order['lines']&.find { |el| el['item_number'] == line_item.variant.sku }
|
42
30
|
|
43
31
|
return 'n/a' unless item
|
44
32
|
|
@@ -95,10 +83,8 @@ module Spree # rubocop:disable Metrics/ModuleLength
|
|
95
83
|
end
|
96
84
|
|
97
85
|
# shows localized total, if possible. if not, fall back to Spree default
|
98
|
-
def
|
99
|
-
|
100
|
-
price = flow_order&.total&.label
|
101
|
-
price || FlowcommerceSpree::Api.format_default_price(total)
|
86
|
+
def flow_io_total_amount
|
87
|
+
flow_data&.dig('order', 'total', 'amount')&.to_d
|
102
88
|
end
|
103
89
|
|
104
90
|
def flow_experience
|
@@ -108,10 +94,6 @@ module Spree # rubocop:disable Metrics/ModuleLength
|
|
108
94
|
model.new ENV.fetch('FLOW_BASE_COUNTRY')
|
109
95
|
end
|
110
96
|
|
111
|
-
def flow_io_checkout_token
|
112
|
-
flow_data&.[]('checkout_token')
|
113
|
-
end
|
114
|
-
|
115
97
|
def flow_io_experience_key
|
116
98
|
flow_data&.[]('exp')
|
117
99
|
end
|
@@ -124,19 +106,10 @@ module Spree # rubocop:disable Metrics/ModuleLength
|
|
124
106
|
flow_data&.dig('order', 'id')
|
125
107
|
end
|
126
108
|
|
127
|
-
def flow_io_session_expires_at
|
128
|
-
flow_data&.[]('session_expires_at')&.to_datetime
|
129
|
-
end
|
130
|
-
|
131
109
|
def flow_io_attributes
|
132
110
|
flow_data&.dig('order', 'attributes') || {}
|
133
111
|
end
|
134
112
|
|
135
|
-
def add_flow_checkout_token(token)
|
136
|
-
self.flow_data ||= {}
|
137
|
-
self.flow_data['checkout_token'] = token
|
138
|
-
end
|
139
|
-
|
140
113
|
def flow_io_attribute_add(attr_key, value)
|
141
114
|
self.flow_data['order'] ||= {}
|
142
115
|
self.flow_data['order']['attributes'] ||= {}
|
@@ -153,35 +126,26 @@ module Spree # rubocop:disable Metrics/ModuleLength
|
|
153
126
|
flow_data&.dig('order', 'attributes', 'user_uuid')
|
154
127
|
end
|
155
128
|
|
156
|
-
def
|
157
|
-
|
158
|
-
|
159
|
-
checkout_token = flow_io_checkout_token
|
160
|
-
return "https://checkout.flow.io/tokens/#{checkout_token}" if checkout_token
|
129
|
+
def flow_io_captures
|
130
|
+
flow_data&.[]('captures')
|
161
131
|
end
|
162
132
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
payments.where(amount: 0, state: %w[invalid processing pending]).map(&:destroy)
|
169
|
-
end
|
133
|
+
def flow_io_captures_sum
|
134
|
+
captures_sum = 0
|
135
|
+
flow_data&.[]('captures')&.each do |c|
|
136
|
+
next if c['status'] != 'succeeded'
|
170
137
|
|
171
|
-
|
172
|
-
|
138
|
+
captures_sum += c['amount']
|
139
|
+
end
|
140
|
+
captures_sum.to_d
|
173
141
|
end
|
174
142
|
|
175
|
-
def
|
176
|
-
flow_data
|
143
|
+
def flow_io_balance_amount
|
144
|
+
flow_data&.dig('order', 'balance', 'amount')&.to_d
|
177
145
|
end
|
178
146
|
|
179
|
-
|
180
|
-
|
181
|
-
def flow_finalize!
|
182
|
-
finalize! unless state == 'complete'
|
183
|
-
update_column :payment_state, 'paid' if payment_state != 'paid'
|
184
|
-
update_column :state, 'complete' if state != 'complete'
|
147
|
+
def flow_io_payments
|
148
|
+
flow_data.dig('order', 'payments')
|
185
149
|
end
|
186
150
|
|
187
151
|
def flow_payment_method
|
@@ -228,17 +192,19 @@ module Spree # rubocop:disable Metrics/ModuleLength
|
|
228
192
|
s_address = flow_ship_address
|
229
193
|
|
230
194
|
if s_address&.changes&.any?
|
231
|
-
s_address.save
|
195
|
+
s_address.save!
|
232
196
|
address_attributes[:ship_address_id] = s_address.id unless ship_address_id
|
233
197
|
end
|
234
198
|
|
235
199
|
b_address = flow_bill_address
|
236
200
|
if b_address&.changes&.any?
|
237
|
-
b_address.save
|
201
|
+
b_address.save!
|
238
202
|
address_attributes[:bill_address_id] = b_address.id unless bill_address_id
|
239
203
|
end
|
240
204
|
|
241
205
|
address_attributes
|
242
206
|
end
|
207
|
+
|
208
|
+
Spree::Order.include(self) if Spree::Order.included_modules.exclude?(self)
|
243
209
|
end
|
244
210
|
end
|
@@ -5,6 +5,8 @@
|
|
5
5
|
module Spree
|
6
6
|
class Gateway
|
7
7
|
class FlowIo < Gateway
|
8
|
+
REFUND_SUCCESS = 'succeeded'
|
9
|
+
|
8
10
|
def provider_class
|
9
11
|
self.class
|
10
12
|
end
|
@@ -23,7 +25,7 @@ module Spree
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def method_type
|
26
|
-
'
|
28
|
+
'flow_io_gateway'
|
27
29
|
end
|
28
30
|
|
29
31
|
def preferences
|
@@ -40,34 +42,29 @@ module Spree
|
|
40
42
|
order.cc_authorization
|
41
43
|
end
|
42
44
|
|
43
|
-
def
|
44
|
-
order
|
45
|
-
|
46
|
-
|
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
|
45
|
+
def refund(payment, amount, _options = {})
|
46
|
+
request_refund_store_result(payment.order, amount)
|
47
|
+
rescue StandardError => e
|
48
|
+
ActiveMerchant::Billing::Response.new(false, e.to_s, {}, {})
|
57
49
|
end
|
58
50
|
|
59
|
-
def
|
60
|
-
|
61
|
-
order.
|
51
|
+
def cancel(authorization)
|
52
|
+
original_payment = Spree::Payment.find_by(response_code: authorization)
|
53
|
+
request_refund_store_result(original_payment.order, original_payment.amount)
|
54
|
+
rescue StandardError => e
|
55
|
+
ActiveMerchant::Billing::Response.new(false, e.to_s, {}, {})
|
62
56
|
end
|
63
57
|
|
64
|
-
def void(
|
65
|
-
|
58
|
+
def void(authorization_id, _source, options = {})
|
59
|
+
amount = (options[:subtotal] + options[:shipping]) * 0.01
|
60
|
+
reversal_form = Io::Flow::V0::Models::ReversalForm.new(key: options[:order_id],
|
61
|
+
authorization_id: authorization_id,
|
62
|
+
amount: amount,
|
63
|
+
currency: options[:currency])
|
64
|
+
FlowcommerceSpree.client.reversals.post(FlowcommerceSpree::ORGANIZATION, reversal_form)
|
66
65
|
end
|
67
66
|
|
68
67
|
def create_profile(payment)
|
69
|
-
# binding.pry
|
70
|
-
|
71
68
|
# payment.order.state
|
72
69
|
@credit_card = payment.source
|
73
70
|
|
@@ -77,12 +74,52 @@ module Spree
|
|
77
74
|
|
78
75
|
private
|
79
76
|
|
77
|
+
def request_refund_store_result(order, amount)
|
78
|
+
refund_form = Io::Flow::V0::Models::RefundForm.new(order_number: order.number,
|
79
|
+
amount: amount,
|
80
|
+
currency: order.currency)
|
81
|
+
response = FlowcommerceSpree.client.refunds.post(FlowcommerceSpree::ORGANIZATION, refund_form)
|
82
|
+
response_status = response.status.value
|
83
|
+
if response_status == REFUND_SUCCESS
|
84
|
+
add_refund_to_order(response, order)
|
85
|
+
map_refund_to_payment(response, order)
|
86
|
+
ActiveMerchant::Billing::Response.new(true, REFUND_SUCCESS, {}, {})
|
87
|
+
else
|
88
|
+
msg = "Partial refund fail. Details: #{response_status}"
|
89
|
+
ActiveMerchant::Billing::Response.new(false, msg, {}, {})
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_refund_to_order(response, order)
|
94
|
+
order.flow_data ||= {}
|
95
|
+
order.flow_data['refunds'] ||= []
|
96
|
+
order_refunds = order.flow_data['refunds']
|
97
|
+
order_refunds.delete_if { |r| r['id'] == response.id }
|
98
|
+
order_refunds << response.to_hash
|
99
|
+
order.update_column(:meta, order.meta.to_json)
|
100
|
+
end
|
101
|
+
|
102
|
+
def map_refund_to_payment(response, order)
|
103
|
+
original_payment = Spree::Payment.find_by(response_code: response.authorization.id)
|
104
|
+
payment = order.payments.create!(state: 'completed',
|
105
|
+
response_code: response.authorization.id,
|
106
|
+
payment_method_id: original_payment&.payment_method_id,
|
107
|
+
amount: - response.amount,
|
108
|
+
source_id: original_payment&.source_id,
|
109
|
+
source_type: original_payment&.source_type)
|
110
|
+
|
111
|
+
# For now this additional update is overwriting the generated identifier with flow.io payment identifier.
|
112
|
+
# TODO: Check and possibly refactor in Spree 3.0, where the `before_create :set_unique_identifier`
|
113
|
+
# has been removed.
|
114
|
+
payment.update_column(:identifier, response.id)
|
115
|
+
end
|
116
|
+
|
80
117
|
# hard inject Flow as payment method unless defined
|
81
118
|
def profile_ensure_payment_method_is_present!
|
82
119
|
return if @credit_card.payment_method_id
|
83
120
|
|
84
|
-
|
85
|
-
@credit_card.payment_method_id =
|
121
|
+
flow_payment_method = Spree::PaymentMethod.find_by(active: true, type: 'Spree::Gateway::FlowIo')
|
122
|
+
@credit_card.payment_method_id = flow_payment_method.id if flow_payment_method
|
86
123
|
end
|
87
124
|
|
88
125
|
# create payment profile with Flow and tokenize Credit Card
|