flowcommerce-activemerchant 0.1.3 → 0.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3cf3aa9eacfd0f8e012d25a368434cfdeeadcaba
4
- data.tar.gz: 274d8b6630325f87041bc7f3e5d7064fabb3b5f5
3
+ metadata.gz: fd3cd7c71165eb856d2df8a62f381c4a345216c2
4
+ data.tar.gz: 2da46342bd287ac1400862e00ab21d61d43b62e7
5
5
  SHA512:
6
- metadata.gz: d0c0387ad80950fce612a269ae46430f1aa11397e06c96875caac884c307e1d7b2c624979ec8813fb0fc29c866e93af5b710e4888da787b82a44d834231e21be
7
- data.tar.gz: 0d5d2f2168cbd65124df94c6aa3378df21364b2ce2ba5a6cf5fbc020c29107684ed189016f6d5b07e45f0f00100ed4fb90e8f4faf383ea53f386a72b1121c3bc
6
+ metadata.gz: 3531b26248d5a5015dc02786d73176b7e80b1e7ebac09a786a8ff4198fb22f0f478eb61080b643e8c38d16d794a3fd66cacc44477bb1775504731fa913cd7429
7
+ data.tar.gz: b9c3740fc926e3060403e9adfd64610d45a2534bf1d4ca0fcf19fa833a45fce930a8e8d2af43a477ee7a34dcb2296e823392949700bfee6c75b0f4936b10badc
data/.version ADDED
@@ -0,0 +1 @@
1
+ 0.1.4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 - 2018 Flow Commerce Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,4 +1,4 @@
1
- # @Flow.io (2017)
1
+ # @Flow.io (2017)
2
2
  # Active Merchant adapter for Flow api
3
3
 
4
4
  require 'flowcommerce-reference'
@@ -6,7 +6,15 @@ require 'flowcommerce-reference'
6
6
  module ActiveMerchant
7
7
  module Billing
8
8
  class FlowGateway < Gateway
9
- VERSION = '0.1.3' unless defined?(::ActiveMerchant::Billing::FlowGateway::VERSION)
9
+ unless defined?(::ActiveMerchant::Billing::FlowGateway::VERSION)
10
+ VERSION = File.read(File.expand_path("../../../../.version", File.dirname(__FILE__))).chomp
11
+
12
+ FORM_TYPES = [
13
+ :authorization_copy_form, :direct_authorization_form, :merchant_of_record_authorization_form,
14
+ :paypal_authorization_form, :redirect_authorization_form, :inline_authorization_form,
15
+ :card_authorization_form, :ach_authorization_form
16
+ ]
17
+ end
10
18
 
11
19
  self.display_name = 'Flow.io Pay'
12
20
  self.homepage_url = 'https://www.flow.io/'
@@ -14,6 +22,7 @@ module ActiveMerchant
14
22
  self.supported_countries = FlowCommerce::Reference::Countries::ISO_3166_2
15
23
  self.supported_cardtypes = FlowCommerce::Reference::PaymentMethods::SUPPORTED_CREDIT_CARDS
16
24
 
25
+
17
26
  def initialize options = {}
18
27
  @flow_api_key = options[:api_key] || ENV['FLOW_API_KEY']
19
28
  @flow_organization = options[:organization] || ENV['FLOW_ORGANIZATION']
@@ -24,153 +33,194 @@ module ActiveMerchant
24
33
  super
25
34
  end
26
35
 
36
+ # Create a new authorization.
27
37
  # https://docs.flow.io/module/payment/resource/authorizations#post-organization-authorizations
28
- def authorize amount, payment_method, options={}
29
- amount = assert_currency options[:currency], amount
30
-
31
- response = get_flow_cc_token payment_method
32
-
33
- data = {
34
- token: response.token,
35
- amount: amount,
36
- currency: options[:currency],
37
- cvv: payment_method.verification_value,
38
- customer: {
39
- name: {
40
- first: payment_method.first_name,
41
- last: payment_method.last_name
42
- }
43
- }
44
- }
38
+ def authorize cc_or_token, order_number, opts={}
39
+ unless opts[:currency]
40
+ return error_response('Currency is a required option')
41
+ end
45
42
 
46
- begin
47
- authorization_form = if options[:order_id]
48
- # order_number allready present at flow
49
- data[:order_number] = options[:order_id]
50
- ::Io::Flow::V0::Models::MerchantOfRecordAuthorizationForm.new data
51
- else
52
- ::Io::Flow::V0::Models::DirectAuthorizationForm.new data
53
- end
54
-
55
- response = flow_instance.authorizations.post @flow_organization, authorization_form
56
- rescue => exception
57
- return Response.new false, exception.message, { exception: exception }
43
+ unless opts[:discriminator]
44
+ return error_response 'Discriminator is not defined, please choose one [%s]' % FORM_TYPES.join(', ')
58
45
  end
59
46
 
60
- options = { response: response }
47
+ unless FORM_TYPES.include?(opts[:discriminator].to_sym)
48
+ return error_response 'Discriminator [%s] not found, please choose one [%s]' % [opts[:discriminator], FORM_TYPES.join(', ')]
49
+ end
61
50
 
62
- if ['review', 'authorized'].include?(response.result.status.value)
63
- store = {
64
- key: response.key,
65
- amount: response.amount,
66
- currency: response.currency,
67
- authorization_id: response.id
68
- }
51
+ body = {
52
+ amount: opts[:amount] || 0.0,
53
+ currency: opts[:currency],
54
+ discriminator: opts[:discriminator],
55
+ token: store(cc_or_token),
56
+ order_number: order_number
57
+ }
69
58
 
70
- Response.new true, 'Flow authorize - Success', options, { authorization: store }
71
- else
72
- Response.new false, 'Flow authorize - Error', options
73
- end
59
+ response = flow_instance.authorizations.post @flow_organization, body
60
+
61
+ Response.new true, 'Flow authorize - Success', { response: response }, { authorization: response.id }
62
+ rescue => exception
63
+ error_response exception
64
+ end
65
+
66
+ # https://docs.flow.io/module/payment/resource/authorizations#get-organization-authorizations
67
+ def flow_get_authorization order_number:
68
+ response = flow_instance.authorizations.get @flow_organization, order_number: order_number
69
+ response.last
70
+ rescue => exception
71
+ error_response exception
74
72
  end
75
73
 
76
74
  # https://docs.flow.io/module/payment/resource/captures#post-organization-captures
77
- def capture _money, authorization, options={}
78
- raise ArgumentError, 'No Authorization authorization, please authorize first' unless authorization
75
+ def capture amount, authorization_key, options={}
76
+ return error_response('Currency is a required option') unless options[:currency]
77
+
78
+ body = {
79
+ authorization_id: authorization_key,
80
+ amount: amount,
81
+ currency: options[:currency]
82
+ }
79
83
 
80
84
  begin
81
- capture_form = ::Io::Flow::V0::Models::CaptureForm.new authorization
85
+ capture_form = ::Io::Flow::V0::Models::CaptureForm.new body
82
86
  response = flow_instance.captures.post @flow_organization, capture_form
83
87
  rescue => exception
84
88
  error_response exception
85
89
  end
86
90
 
87
- options = { response: response }
88
-
89
- if response.id
90
- Response.new true, 'Flow capture - Success', options
91
+ if response.try(:id)
92
+ Response.new true, 'Flow capture - Success', { response: response }
91
93
  else
92
- Response.new false, 'Flow capture - Error', options
94
+ Response.new false, 'Flow capture - Error', { response: response }
93
95
  end
96
+ rescue Io::Flow::V0::HttpClient::ServerError => exception
97
+ error_response exception
94
98
  end
95
99
 
96
- def purchase money, credit_card, options={}
97
- response = authorize money, credit_card, options
98
- capture money, response.authorization
100
+ def purchase credit_card, order_number, options={}
101
+ response = authorize credit_card, order_number, options
102
+ capture options[:amount], response.authorization, options
99
103
  end
100
104
 
101
- # https://docs.flow.io/module/payment/resource/authorizations#delete-organization-authorizations-key
102
- def void money, authorization_key, options={}
103
- response = flow_instance.authorizations.delete_by_key @flow_organization, authorization_key
105
+ # https://docs.flow.io/module/payment/resource/reversals#post-organization-reversals
106
+ # if amount is not provided, reverse the full or remaining amount
107
+ def void amount, authorization_id, options={}
108
+ options[:authorization_id] = authorization_id
109
+
110
+ if amount
111
+ options[:amount] = assert_currency options[:currency], amount
112
+ end
113
+
114
+ response = flow_instance.reversals.post @flow_organization, options
115
+
104
116
  Response.new true, 'void success', { response: response }
105
117
  rescue Io::Flow::V0::HttpClient::ServerError => exception
106
- error_response(exception)
118
+ error_response exception
107
119
  end
108
120
 
109
121
  # https://docs.flow.io/module/payment/resource/refunds
110
122
  # authorization_id - The Id of the authorization against which to issue the refund. If specified, we will look at all captures for this authorization, selecting 1 or more captures against which to issue the refund of the requested amount.
111
123
  # capture_id - The Id of the capture against which to issue the refund. If specified, we will only consider this capture.
112
124
  # order_number - The order number if specified during authorization. If specified, we will lookup all authorizations made against this order number, and then selecting 1 or more authorizations against which to issue the refund of the requested amount.
113
- # key - Your unique identifier for this transaction, which if provided is used to implement idempotency. If not provided, we will assign.
114
125
  # amount - The amount to refund, in the currency of the associated capture. Defaults to the value of the capture minus any prior refunds.
115
126
  # currency - The ISO 4217-3 code for the currency. Required if amount is specified. Case insensitive. Note you will get an error if the currency does not match the related authrization's currency. See https://api.flow.io/reference/currencies
116
127
  # rma_key - The RMA key, if available. If specified, this will udpate the RMA status as refunded.
117
128
  def refund amount, capture_id, options={}
118
- refund_form = {}
119
- refund_form[:amount] = amount if amount
120
- refund_form[:capture_id] = capture_id if capture_id
129
+ options[:capture_id] = capture_id if capture_id
121
130
 
122
- [:authorization_id, :currency, :order_number, :key, :rma_key].each do |key|
123
- refund_form[key] = options[key] if options[key]
131
+ if amount
132
+ options[:amount] = assert_currency options[:currency], amount
124
133
  end
125
134
 
126
- if refund_form[:amount]
127
- raise ArgumentError, 'Currency is required if amount is provided' unless refund_form[:currency]
128
- refund_form[:amount] = assert_currency refund_form[:currency], refund_form[:amount]
129
- end
135
+ response = flow_instance.refunds.post @flow_organization, options
130
136
 
131
- refund_form = ::Io::Flow::V0::Models::RefundForm.new refund_form
132
- flow_instance.refunds.post @flow_organization, refund_form
133
- end
134
-
135
- # store credit card with flow and get reference token
136
- def store credit_card, options={}
137
- response = get_flow_cc_token credit_card
138
- Response.new true, 'Credit card stored', { response: response, token: response.token }
137
+ if response.try(:id)
138
+ Response.new true, 'Flow refund - Success', { response: response }
139
+ else
140
+ Response.new false, 'Flow refund - Error', { response: response }
141
+ end
139
142
  rescue Io::Flow::V0::HttpClient::ServerError => exception
140
143
  error_response exception
141
144
  end
142
145
 
143
- private
144
-
145
- def flow_instance
146
- FlowCommerce.instance token: @flow_api_key
147
- end
146
+ # stores credit card and returns credit card Flow token String id
147
+ def store input
148
+ credit_card =
149
+ case input
150
+ when Hash
151
+ ActiveMerchant::Billing::CreditCard.new input
152
+ when ActiveMerchant::Billing::CreditCard
153
+ input
154
+ when String
155
+ return input
156
+ else
157
+ raise 'Unsuported store method input type [%s]' % input.class
158
+ end
148
159
 
149
- def get_flow_cc_token credit_card
150
160
  data = { number: credit_card.number,
151
161
  name: '%s %s' % [credit_card.first_name, credit_card.last_name],
152
162
  cvv: credit_card.verification_value,
153
163
  expiration_year: credit_card.year.to_i,
154
164
  expiration_month: credit_card.month.to_i
165
+ address: credit_card.address
155
166
  }
156
167
 
157
- card_form = ::Io::Flow::V0::Models::CardForm.new data
158
- flow_instance.cards.post @flow_organization, card_form
168
+ response = flow_instance.cards.post @flow_organization, data
169
+
170
+ if response.respond_to?(:token)
171
+ Response.new true, 'Flow refund - Success', { response: response }
172
+ else
173
+ Response.new false, 'Flow POST /:organization/cards - Failure', { response: response }
174
+ end
175
+ rescue Io::Flow::V0::HttpClient::ServerError => exception
176
+ error_response exception
177
+ end
178
+
179
+ # Creates and order
180
+ # https://docs.flow.io/module/localization/resource/orders#post-organization-orders
181
+ def flow_create_order body, query_string={}
182
+ flow_instance.orders.post @flow_organization, body, query_string
183
+ rescue => exception
184
+ error_response exception
185
+ end
186
+
187
+ # Submits an order and
188
+ # pushes all subsequent authorizations from status "review" to "authorized"
189
+ # takes ~ 60 seconds
190
+ def flow_submission_by_number order_number
191
+ flow_instance.orders.put_submissions_by_number @flow_organization, order_number
192
+ rescue => exception
193
+ error_response exception
194
+ end
195
+
196
+ private
197
+
198
+ def flow_instance
199
+ FlowCommerce.instance token: @flow_api_key
159
200
  end
160
201
 
161
202
  def error_response exception_object
162
- message = if exception_object.respond_to?(:body) && exception_object.body.length > 0
203
+ message =
204
+ if exception_object.is_a?(String)
205
+ exception_object
206
+ elsif exception_object.respond_to?(:body) && exception_object.body.length > 0
163
207
  description = JSON.load(exception_object.body)['messages'].to_sentence
164
208
  '%s: %s (%s)' % [exception_object.details, description, exception_object.code]
165
- else
209
+ elsif exception_object.respond_to?(:message)
166
210
  exception_object.message
211
+ else
212
+ raise ArgumentError.new('Unsuported exception_object [%s]' % exception_object.class)
167
213
  end
168
214
 
215
+ msg = 'ERROR: %s' % message
216
+ msg = msg.yellow if msg.respond_to?(:yellow)
217
+
218
+ puts msg
219
+
169
220
  Response.new false, message, exception: exception_object
170
221
  end
171
222
 
172
223
  def assert_currency currency, amount
173
- raise ArgumentError, 'currency not provided' unless currency
174
224
  FlowCommerce::Reference::Currencies.find! currency
175
225
  amount.to_f
176
226
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flowcommerce-activemerchant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dino Reic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-09-07 00:00:00.000000000 Z
11
+ date: 2018-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemerchant
@@ -59,6 +59,8 @@ executables: []
59
59
  extensions: []
60
60
  extra_rdoc_files: []
61
61
  files:
62
+ - "./.version"
63
+ - "./LICENSE"
62
64
  - "./lib/active_merchant/billing/gateways/flow.rb"
63
65
  - "./lib/flowcommerce-activemerchant.rb"
64
66
  homepage: https://www.flow.io