omnipay 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/omnipay.rb +10 -5
- data/lib/omnipay/adapter.rb +272 -0
- data/lib/omnipay/configuration.rb +5 -1
- data/lib/omnipay/gateway.rb +50 -12
- data/lib/omnipay/gateways.rb +11 -2
- data/lib/omnipay/helpers.rb +15 -0
- data/lib/omnipay/middleware.rb +51 -7
- data/lib/omnipay/railtie.rb +7 -3
- data/lib/omnipay/sample_adapter.rb +114 -0
- data/lib/omnipay/version.rb +1 -1
- metadata +6 -8
- data/lib/omnipay/adapters/mangopay.rb +0 -138
- data/lib/omnipay/adapters/mangopay/client.rb +0 -71
- data/lib/omnipay/adapters/oneclicpay.rb +0 -132
- data/lib/omnipay/adapters/sample_adapter.rb +0 -115
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 426ecbaa0b5c6679ed1270a5f4e95d3b0e701724
|
4
|
+
data.tar.gz: d888f625b19af11cfb97ac2980187da74bcf29c8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e426e0bc7aeaf2da37c3fedf6ea1c42c9588f02dd3916cc86906418a14a3364c40261a18e1c3edbeffaf3f1433d11a016d9f265335829959e9e6eb5a32e98c82
|
7
|
+
data.tar.gz: 55ff42d3659b4e787a4050c9cedb41b0c2f385076517a7f1a9d3f1c5d1d36734e4c8de0a003661a5eb6096d241a5c7e4d69857950ebbb46c00107b570c9393a5
|
data/lib/omnipay.rb
CHANGED
@@ -1,14 +1,17 @@
|
|
1
1
|
# The root Omnipay module. Used for defining its global configuration
|
2
2
|
module Omnipay
|
3
3
|
|
4
|
+
autoload :Adapter, 'omnipay/adapter'
|
4
5
|
autoload :Gateway, 'omnipay/gateway'
|
5
6
|
autoload :AutosubmitForm, 'omnipay/autosubmit_form'
|
6
7
|
autoload :Configuration, 'omnipay/configuration'
|
7
8
|
autoload :Gateways, 'omnipay/gateways'
|
8
9
|
autoload :Middleware, 'omnipay/middleware'
|
10
|
+
autoload :Helpers, 'omnipay/helpers'
|
9
11
|
|
10
|
-
|
11
|
-
|
12
|
+
|
13
|
+
# Code for a successful transaction
|
14
|
+
SUCCESS = :success
|
12
15
|
|
13
16
|
# Error code for a user-initiated payment failure
|
14
17
|
CANCELATION = :cancelation
|
@@ -16,18 +19,20 @@ module Omnipay
|
|
16
19
|
# Error code for a valid response but a failed payment
|
17
20
|
PAYMENT_REFUSED = :payment_refused
|
18
21
|
|
19
|
-
# Error code for
|
20
|
-
|
22
|
+
# Error code for an untreatable response
|
23
|
+
INVALID_RESPONSE = :invalid_response
|
21
24
|
|
22
25
|
|
23
26
|
|
24
27
|
# Accessors to the configured gateways
|
28
|
+
# @return [Gateways] the configured gateways
|
25
29
|
def self.gateways
|
26
30
|
@gateways ||= Omnipay::Gateways.new
|
27
31
|
end
|
28
32
|
|
29
33
|
|
30
34
|
# Syntaxic sugar for adding a new gateway
|
35
|
+
# @see Gateways#push
|
31
36
|
def self.use_gateway(opts = {}, &block)
|
32
37
|
self.gateways.push(opts, &block)
|
33
38
|
end
|
@@ -44,7 +49,7 @@ module Omnipay
|
|
44
49
|
# Example use :
|
45
50
|
#
|
46
51
|
# Omnipay.configure do |config|
|
47
|
-
# config.
|
52
|
+
# config.base_path = '/payment'
|
48
53
|
# end
|
49
54
|
def self.configure
|
50
55
|
yield configuration
|
@@ -0,0 +1,272 @@
|
|
1
|
+
module Omnipay
|
2
|
+
|
3
|
+
require 'ostruct'
|
4
|
+
|
5
|
+
# Base adapter class
|
6
|
+
# Inherited by specific implementations
|
7
|
+
class Adapter
|
8
|
+
|
9
|
+
# Handle adapter and payment configuration
|
10
|
+
# Can be defined
|
11
|
+
# - at the class level
|
12
|
+
# - at the adapter initialization
|
13
|
+
# - at the payment redirection (only payment config)
|
14
|
+
# Adapter config is mandatory
|
15
|
+
# Payment config is not, but extra fields are
|
16
|
+
|
17
|
+
ConfigField = Struct.new(:explaination, :value, :mandatory)
|
18
|
+
|
19
|
+
DEFAULT_ADAPTER_CONFIG = {
|
20
|
+
:sandbox => ConfigField.new('when enabled, the payment are not actually withdrawn', true, nil)
|
21
|
+
}
|
22
|
+
|
23
|
+
DEFAULT_PAYMENT_CONFIG = {
|
24
|
+
:amount => ConfigField.new('The amount (in cents) to pay', nil, true),
|
25
|
+
:reference => ConfigField.new('The local reference of the payment', nil, true),
|
26
|
+
:currency => ConfigField.new('The ISO 4217 code for the currency to use', 'USD', true),
|
27
|
+
:locale => ConfigField.new('The ISO 639-1 locale code for the payment page', 'en', true),
|
28
|
+
:title => ConfigField.new('The title to display on the payment page', nil, false),
|
29
|
+
:description => ConfigField.new('A description to display on the payment page', nil, false),
|
30
|
+
:template_url => ConfigField.new('The url for the template to use on the payment page', nil, false)
|
31
|
+
}
|
32
|
+
|
33
|
+
|
34
|
+
# ========================
|
35
|
+
# Setup the adapter config
|
36
|
+
# ========================
|
37
|
+
|
38
|
+
# Accessor to the adapter config
|
39
|
+
def self.adapter_config
|
40
|
+
@adapter_config ||= Omnipay::Helpers.deep_dup(DEFAULT_ADAPTER_CONFIG)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add a new config field to the adapter.
|
44
|
+
# Is mandatory and unfilled.
|
45
|
+
# If default value or optional => should be a payment config
|
46
|
+
def self.config(name, explaination)
|
47
|
+
adapter_config[name] = ConfigField.new(explaination, nil, true)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
# ====================================
|
52
|
+
# Setup the payment redirection config
|
53
|
+
# ====================================
|
54
|
+
|
55
|
+
# Accessor to the payment config
|
56
|
+
def self.payment_config
|
57
|
+
@payment_config ||= Omnipay::Helpers.deep_dup(DEFAULT_PAYMENT_CONFIG)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Add a new field to the payment config
|
61
|
+
# Options are :mandatory (default false) and :default (default nil)
|
62
|
+
def self.custom_payment_param(name, explaination, opts = {})
|
63
|
+
if payment_config.has_key?(name)
|
64
|
+
raise ArgumentError, "Cannot add custom payment param #{name}, as it already exists and is : #{payment_config[name].explaination}"
|
65
|
+
end
|
66
|
+
payment_config[name] = ConfigField.new(explaination, opts[:default], !!opts[:mandatory])
|
67
|
+
end
|
68
|
+
|
69
|
+
# Setup default values for existing payment params
|
70
|
+
def self.default_payment_params(default_values)
|
71
|
+
default_values.each do |name, default_value|
|
72
|
+
payment_config[name] && ( payment_config[name].value = default_value )
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# ===================
|
78
|
+
# Setup if IPN or not
|
79
|
+
# ===================
|
80
|
+
|
81
|
+
def self.enable_ipn
|
82
|
+
@ipn_enabled = true
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.ipn?
|
86
|
+
!!@ipn_enabled
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# =============================
|
91
|
+
# Actual adapter instance logic
|
92
|
+
# =============================
|
93
|
+
|
94
|
+
def initialize(config = {})
|
95
|
+
payment_config = config.delete(:payment)
|
96
|
+
|
97
|
+
@adapter_config = build_adapter_config(config)
|
98
|
+
@payment_config = build_payment_config(payment_config)
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# Config easy readers
|
103
|
+
# from : {:foo => ConfigField(..., value='bar')}
|
104
|
+
# to : config.foo # => 'bar'
|
105
|
+
def config
|
106
|
+
@config ||= OpenStruct.new(Hash[@adapter_config.map{|name, config_field| [name, config_field.value]}])
|
107
|
+
end
|
108
|
+
|
109
|
+
def default_payment_params
|
110
|
+
@default_payment_params ||= OpenStruct.new(Hash[@payment_config.map{|name, config_field| [name, config_field.value]}])
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
# ================
|
115
|
+
# Public interface
|
116
|
+
# ================
|
117
|
+
|
118
|
+
def request_phase(params, ipn_url, callback_url)
|
119
|
+
payment_params = Omnipay::Helpers.deep_dup(@payment_config)
|
120
|
+
|
121
|
+
# Merge params with default payment config
|
122
|
+
params.each do |name, value|
|
123
|
+
payment_params[name] && payment_params[name].value = value
|
124
|
+
end
|
125
|
+
|
126
|
+
# Validate payment params
|
127
|
+
payment_params.each do |name, config_field|
|
128
|
+
if config_field.mandatory && config_field.value.nil?
|
129
|
+
raise ArgumentError, "Mandatory payment parameter #{name} is not defined. It is supposed to be #{config_field.explaination}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# {name => config_field} to OpenStruct(name => value)
|
134
|
+
payment_params = OpenStruct.new(
|
135
|
+
Hash[ payment_params.map{|name, config_field| [name, config_field.value]} ]
|
136
|
+
)
|
137
|
+
|
138
|
+
# Forward to the right method (redirection_with_ipn or redirection)
|
139
|
+
if self.class.ipn?
|
140
|
+
return payment_page_redirection_ipn(payment_params, ipn_url, callback_url)
|
141
|
+
else
|
142
|
+
return payment_page_redirection(payment_params, callback_url)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
def ipn_hash(request)
|
148
|
+
request = Rack::Request.new(request.env.dup)
|
149
|
+
return validate_payment_notification(request)
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
def callback_hash(request)
|
154
|
+
request = Rack::Request.new(request.env.dup)
|
155
|
+
return validate_callback_status(request)
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
# ===============================
|
160
|
+
# Logic to redefine in subclasses
|
161
|
+
# ===============================
|
162
|
+
|
163
|
+
def payment_page_redirection(params, callback_url)
|
164
|
+
raise NoMethodError, "To redefine in adapter implementation"
|
165
|
+
end
|
166
|
+
|
167
|
+
def payment_page_ipn_redirection(params, ipn_url, callback_url)
|
168
|
+
raise NoMethodError, "To redefine in adapter implementation"
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
def validate_payment_notification(request)
|
173
|
+
raise NoMethodError, "To redefine in adapter implementation"
|
174
|
+
end
|
175
|
+
|
176
|
+
|
177
|
+
def validate_callback_status(request)
|
178
|
+
raise NoMethodError, "To redefine in adapter implementation"
|
179
|
+
end
|
180
|
+
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
# ==========================
|
185
|
+
# Helpers to format response
|
186
|
+
# ==========================
|
187
|
+
|
188
|
+
def payment_error(message)
|
189
|
+
status_error(message)
|
190
|
+
end
|
191
|
+
|
192
|
+
def payment_failed(reference, reason)
|
193
|
+
status_failed(reason).merge(
|
194
|
+
:reference => reference
|
195
|
+
)
|
196
|
+
end
|
197
|
+
|
198
|
+
def payment_canceled(reference)
|
199
|
+
status_canceled.merge(
|
200
|
+
:reference => reference
|
201
|
+
)
|
202
|
+
end
|
203
|
+
|
204
|
+
def payment_successful(reference, transaction_id, amount)
|
205
|
+
status_successful(reference).merge(
|
206
|
+
:amount => amount,
|
207
|
+
:transaction_id => transaction_id
|
208
|
+
)
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
def status_error(message = '')
|
213
|
+
{:success => false, :status => Omnipay::INVALID_RESPONSE, :error_message => message}
|
214
|
+
end
|
215
|
+
|
216
|
+
def status_failed(message = '')
|
217
|
+
{:success => false, :status => Omnipay::PAYMENT_REFUSED, :error_message => message}
|
218
|
+
end
|
219
|
+
|
220
|
+
def status_canceled
|
221
|
+
{:success => false, :status => Omnipay::CANCELATION}
|
222
|
+
end
|
223
|
+
|
224
|
+
def status_successful(reference)
|
225
|
+
{:success => true, :status => Omnipay::SUCCESS, :reference => reference}
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
private
|
230
|
+
|
231
|
+
# Build the instance adapter config from the class one and a hash of value overrides
|
232
|
+
def build_adapter_config(overrides)
|
233
|
+
# Clone the default config
|
234
|
+
adapter_config = Omnipay::Helpers.deep_dup(self.class.adapter_config)
|
235
|
+
|
236
|
+
# Override its values
|
237
|
+
overrides.each do |name, value|
|
238
|
+
next unless adapter_config.has_key? name
|
239
|
+
adapter_config[name].value = value
|
240
|
+
end
|
241
|
+
|
242
|
+
# Validate it
|
243
|
+
adapter_config.each do |name, config_field|
|
244
|
+
if config_field.mandatory && config_field.value == nil
|
245
|
+
raise ArgumentError, "Mandatory config field #{name} is not defined. It is supposed to be #{config_field.explaination}"
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
return adapter_config
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
# Build the instance payment config from the class one and a hash of value overrides
|
254
|
+
def build_payment_config(overrides)
|
255
|
+
overrides ||= {} # Nil can be passed as an argument
|
256
|
+
|
257
|
+
# Clone the default config
|
258
|
+
payment_config = Omnipay::Helpers.deep_dup(self.class.payment_config)
|
259
|
+
|
260
|
+
# Override its values
|
261
|
+
overrides.each do |name, value|
|
262
|
+
next unless payment_config.has_key? name
|
263
|
+
payment_config[name].value = value
|
264
|
+
end
|
265
|
+
|
266
|
+
# No validation, done at the redirection time
|
267
|
+
return payment_config
|
268
|
+
end
|
269
|
+
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
@@ -3,11 +3,15 @@ require 'singleton'
|
|
3
3
|
module Omnipay
|
4
4
|
|
5
5
|
# The global Omnipay configuration singleton
|
6
|
+
# Can be assigned the following values :
|
7
|
+
# - +base_path+ (default "/pay") : the base path which will be hit in the payment providers callbacks.
|
8
|
+
# - +base_uri+ (default nil) : the base uri (scheme + host + port) which will be hit in the payment providers callbacks.
|
6
9
|
class Configuration
|
7
10
|
include Singleton
|
8
|
-
attr_accessor :base_path
|
11
|
+
attr_accessor :base_path, :base_uri
|
9
12
|
|
10
13
|
def initialize
|
14
|
+
@base_uri = nil
|
11
15
|
@base_path = "/pay"
|
12
16
|
end
|
13
17
|
end
|
data/lib/omnipay/gateway.rb
CHANGED
@@ -1,10 +1,18 @@
|
|
1
1
|
module Omnipay
|
2
2
|
|
3
|
-
# Instance of a gateway connection. Has an uid, and encapsulates an adapter strategy
|
3
|
+
# Instance of a gateway connection. Has an uid, and encapsulates an adapter strategy.
|
4
4
|
class Gateway
|
5
5
|
|
6
6
|
attr_reader :uid, :adapter_class, :config, :adapter
|
7
7
|
|
8
|
+
# @param opts [Hash]
|
9
|
+
#
|
10
|
+
# The options hash must include the following keys :
|
11
|
+
# - :uid => the gateways uid
|
12
|
+
# - :adapter => the adapter class
|
13
|
+
#
|
14
|
+
# The options hash may also include the following keys :
|
15
|
+
# - :config => the configration hash passed to the adapter for its initialization
|
8
16
|
def initialize(opts = {})
|
9
17
|
@uid = opts[:uid]
|
10
18
|
@adapter_class = opts[:adapter]
|
@@ -18,19 +26,23 @@ module Omnipay
|
|
18
26
|
|
19
27
|
|
20
28
|
# The Rack::Response corresponding to the redirection to the payment page
|
29
|
+
# @param opts [Hash] The attributes of the current payment. Will be passed on to the adapter.
|
30
|
+
# The options hash must contain the following keys :
|
31
|
+
# - +:base_uri+ : the current http scheme + host (used in the post-payment redirection)
|
32
|
+
# - +:amount [Integer]+ : the amount to pay, in cents
|
33
|
+
# Depending on the adapter used, the options hash may have other mandatory keys. Refer to the adapter's documentation for more details
|
34
|
+
# @return [Rack::Response] the GET or POST redirection to the payment provider
|
21
35
|
def payment_redirection(opts = {})
|
22
|
-
|
23
|
-
|
36
|
+
base_uri = get_base_uri(opts)
|
37
|
+
raise ArgumentError.new('Missing parameter :base_uri') unless base_uri
|
24
38
|
|
25
|
-
|
26
|
-
|
39
|
+
ipn_url = "#{base_uri}#{Omnipay.configuration.base_path}/#{uid}/ipn"
|
40
|
+
callback_url = "#{base_uri}#{Omnipay.configuration.base_path}/#{uid}/callback"
|
27
41
|
|
28
|
-
|
29
|
-
|
30
|
-
method, url, params = @adapter.request_phase(amount, callback_url, opts)
|
42
|
+
method, url, params = @adapter.request_phase(opts, ipn_url, callback_url)
|
31
43
|
|
32
44
|
if method == 'GET'
|
33
|
-
redirect_url = url + '?' + Rack::Utils.build_query(params)
|
45
|
+
redirect_url = url + (url.include?('?') ? '&' : '?') + Rack::Utils.build_query(params)
|
34
46
|
Rack::Response.new.tap{|response| response.redirect(redirect_url)}
|
35
47
|
|
36
48
|
elsif method == 'POST'
|
@@ -44,12 +56,38 @@ module Omnipay
|
|
44
56
|
end
|
45
57
|
|
46
58
|
|
47
|
-
# The response
|
48
|
-
|
49
|
-
|
59
|
+
# The formatted response hashes
|
60
|
+
# @return [Hash] the processed response which will be present in the request environement under 'omnipay.response'
|
61
|
+
def ipn_hash(request)
|
62
|
+
@adapter.ipn_hash(request).merge(:raw => request.params)
|
63
|
+
end
|
64
|
+
|
65
|
+
def callback_hash(request)
|
66
|
+
@adapter.callback_hash(request).merge(:raw => request.params)
|
50
67
|
end
|
51
68
|
|
52
69
|
|
70
|
+
# Is IPN enabled?
|
71
|
+
def ipn_enabled?
|
72
|
+
@adapter_class.ipn?
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def get_base_uri(opts)
|
79
|
+
base_uri = opts.delete :base_uri
|
80
|
+
|
81
|
+
if !base_uri && opts[:host]
|
82
|
+
base_uri = opts.delete :host
|
83
|
+
Kernel.warn "[DEPRECATION] `host` is deprecated. Please use `base_uri` instead."
|
84
|
+
end
|
85
|
+
|
86
|
+
base_uri ||= Omnipay.configuration.base_uri
|
87
|
+
|
88
|
+
base_uri
|
89
|
+
end
|
90
|
+
|
53
91
|
end
|
54
92
|
|
55
93
|
end
|
data/lib/omnipay/gateways.rb
CHANGED
@@ -3,7 +3,6 @@ module Omnipay
|
|
3
3
|
# Structure responsible for storing and accessing the application's configured gateways
|
4
4
|
class Gateways
|
5
5
|
|
6
|
-
|
7
6
|
def initialize
|
8
7
|
@gateways = {}
|
9
8
|
@dynamic_configs = [] # Collection of procs which given a uid may or may not return a gateway config hash
|
@@ -11,6 +10,14 @@ module Omnipay
|
|
11
10
|
|
12
11
|
|
13
12
|
# Add a new gateway, static or dynamic
|
13
|
+
#
|
14
|
+
# Can be initialized via a config hash, or a block.
|
15
|
+
#
|
16
|
+
# If initialized with a hash, it must contains the `:uid` and `:adapter` keys
|
17
|
+
#
|
18
|
+
# If initialized with a block, the block must take the uid as an argument, and return a config hash with an `:adapter` key
|
19
|
+
# @param opts [Hash] the gateway configuration, if static.
|
20
|
+
# @param block [Proc] the gateway configuration, if dynamic.
|
14
21
|
def push(opts = {}, &block)
|
15
22
|
if block_given?
|
16
23
|
@dynamic_configs << Proc.new(&block)
|
@@ -24,7 +31,9 @@ module Omnipay
|
|
24
31
|
end
|
25
32
|
|
26
33
|
|
27
|
-
# Find
|
34
|
+
# Find a static gateway or instanciate a dynamic gateway for the given uid
|
35
|
+
# @param uid [String] the gateway's uid
|
36
|
+
# @return [Gateway] the corresponding gateway, or nil if none
|
28
37
|
def find(uid)
|
29
38
|
gateway = @gateways[uid]
|
30
39
|
return gateway if gateway
|
data/lib/omnipay/middleware.rb
CHANGED
@@ -18,6 +18,8 @@ module Omnipay
|
|
18
18
|
|
19
19
|
# The standard rack middleware call. Will be processed by an instance of RequestPhase or CallbackPhase if the
|
20
20
|
# path matches the adapter's uid. Will forward to the app otherwise
|
21
|
+
# @param env [Hash] the request's environment
|
22
|
+
# @return [Rack::Reponse]
|
21
23
|
def call(env)
|
22
24
|
|
23
25
|
# Get the current request
|
@@ -31,16 +33,18 @@ module Omnipay
|
|
31
33
|
gateway = Omnipay.gateways.find(uid)
|
32
34
|
return @app.call(env) unless gateway
|
33
35
|
|
36
|
+
# Handle the IPN phase
|
37
|
+
return call_ipn(request, gateway) if ipn_phase?(request, uid)
|
38
|
+
|
34
39
|
# Handle the callback phase
|
35
40
|
if callback_phase?(request, uid)
|
36
|
-
# Symbolize the params keys
|
37
|
-
params = Hash[request.params.map{|k,v|[k.to_sym,v]}]
|
38
41
|
|
39
|
-
#
|
40
|
-
|
42
|
+
# If no IPN : send the ipn request before
|
43
|
+
if !gateway.ipn_enabled?
|
44
|
+
call_ipn(request, gateway, :force => true)
|
45
|
+
end
|
41
46
|
|
42
|
-
|
43
|
-
request.env['REQUEST_METHOD'] = 'GET'
|
47
|
+
return call_callback(request, gateway)
|
44
48
|
end
|
45
49
|
|
46
50
|
# Forward to the app
|
@@ -51,8 +55,48 @@ module Omnipay
|
|
51
55
|
|
52
56
|
private
|
53
57
|
|
58
|
+
def ipn_path(uid)
|
59
|
+
"#{Omnipay.configuration.base_path}/#{uid}/ipn"
|
60
|
+
end
|
61
|
+
|
62
|
+
def callback_path(uid)
|
63
|
+
"#{Omnipay.configuration.base_path}/#{uid}/callback"
|
64
|
+
end
|
65
|
+
|
66
|
+
def ipn_phase?(request, uid)
|
67
|
+
request.path == ipn_path(uid)
|
68
|
+
end
|
69
|
+
|
54
70
|
def callback_phase?(request, uid)
|
55
|
-
request.path ==
|
71
|
+
request.path == callback_path(uid)
|
72
|
+
end
|
73
|
+
|
74
|
+
def call_ipn(request, gateway, opts = {})
|
75
|
+
# Force : override the path
|
76
|
+
if opts[:force]
|
77
|
+
request = Rack::Request.new(request.env.dup)
|
78
|
+
request.path_info = ipn_path(gateway.uid)
|
79
|
+
end
|
80
|
+
|
81
|
+
# Set the formatted response
|
82
|
+
request.env['omnipay.response'] = gateway.ipn_hash(request)
|
83
|
+
|
84
|
+
# Force a POST on the app
|
85
|
+
request.env['REQUEST_METHOD'] = 'POST'
|
86
|
+
|
87
|
+
# Call the app
|
88
|
+
@app.call(request.env)
|
89
|
+
end
|
90
|
+
|
91
|
+
def call_callback(request, gateway)
|
92
|
+
# Set the formatted response
|
93
|
+
request.env['omnipay.response'] = gateway.callback_hash(request)
|
94
|
+
|
95
|
+
# Force a get request
|
96
|
+
request.env['REQUEST_METHOD'] = 'GET'
|
97
|
+
|
98
|
+
# Call the app
|
99
|
+
@app.call(request.env)
|
56
100
|
end
|
57
101
|
|
58
102
|
|
data/lib/omnipay/railtie.rb
CHANGED
@@ -4,10 +4,10 @@ module Omnipay
|
|
4
4
|
module Helpers
|
5
5
|
|
6
6
|
def redirect_to_payment(uid, opts = {})
|
7
|
-
|
7
|
+
base_uri = "#{request.scheme}://#{request.host_with_port}"
|
8
8
|
gateway = Omnipay.gateways.find(uid)
|
9
9
|
if gateway
|
10
|
-
rack_response = gateway.payment_redirection(opts.merge(:
|
10
|
+
rack_response = gateway.payment_redirection(opts.merge(:base_uri => base_uri))
|
11
11
|
|
12
12
|
self.response_body = rack_response.body
|
13
13
|
self.status = rack_response.status
|
@@ -22,7 +22,11 @@ module Omnipay
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
-
|
25
|
+
# Custom helpers for rails applications
|
26
|
+
# Define the method : <b>+ActionController#redirect_to_payment(uid, opts={})+</b>
|
27
|
+
# - <b>+uid+</b> : the gateway's uid
|
28
|
+
# - <b>+opts+</b> : the options expected by Gateway#payment_redirection. The host is automatically determined, but the <b>+amount+</b> in cents, and other mandatory options depending on the adapter, have to be specified.
|
29
|
+
# Called in a controller, this method redirects the visitor to the payment provider.
|
26
30
|
class Railtie < Rails::Railtie
|
27
31
|
|
28
32
|
initializer "omnipay.configure_rails_initialization" do
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Omnipay
|
2
|
+
module Adapters
|
3
|
+
|
4
|
+
# This is a sample Omnipay adapter implementation for a fictive payment provider. You can use it as a boilerplate to develop your own.
|
5
|
+
#
|
6
|
+
# This adapter will take two arguments in its configuration :
|
7
|
+
# * *api_key* [String - mandatory] : given by the payment provider, used for authenticating to their API
|
8
|
+
# * *sandbox* [Boolean - optional] : whether the payment should be done in a sandbox or with the real payment page
|
9
|
+
# It may then be set up like this in a rack application
|
10
|
+
# Omnipay.use_gateway(
|
11
|
+
# :uid => 'my_gateway',
|
12
|
+
# :adapter => Omnipay::Adapters::SampleAdapter,
|
13
|
+
# :config => {
|
14
|
+
# :api_key => 'my-api-key',
|
15
|
+
# :sandbox => (ENV['RACK_ENV'] != 'production')
|
16
|
+
# }
|
17
|
+
# )
|
18
|
+
#
|
19
|
+
# Let's say our payment provider needs to be given the birthdate of the buyer for every payment. It also accepts a locale to display the payment page in
|
20
|
+
# It means that the redirection to the payment provider will be called like this :
|
21
|
+
# my_gateway = Omnipay.gateways.find('my_gateway')
|
22
|
+
# my_gateway.payment_redirection({
|
23
|
+
# :amount => 1295, # Amount in cents, mandatory for every adapter
|
24
|
+
# :host => 'http://www.my-website.com', # Also mandatory for every adapter, used to compute the redirect_url
|
25
|
+
# :birthdate => current_user.birthdate, # Custom field for this adapter class
|
26
|
+
# :locale => 'fr' # Optional field for this adapter class
|
27
|
+
# })
|
28
|
+
class SampleAdapter
|
29
|
+
|
30
|
+
# The adapters initialization
|
31
|
+
# @param config [Hash] the adapter's configuration which will be populated in the application's omnipay config file. For the example above in a dev environment, the value would be {:api_key => 'my-api-key', :sandbox => true}. It is up to you to decide which fields are mandatory, and to both check them in this initializer and specify them in your documentation. In this case :
|
32
|
+
# * +api_key+ : the API key given for your fictive gateway's account. Mandatory
|
33
|
+
# * +sandbox+ : whether to use a sandboxed payment page, or a real one. Optional and defaults to true
|
34
|
+
# @return [SampleAdapter]
|
35
|
+
def initialize(config = {})
|
36
|
+
@api_key = config[:api_key]
|
37
|
+
raise ArgumentError.new("Missing api_key") unless @api_key
|
38
|
+
|
39
|
+
@mode = (config[:sandbox] == false) ? 'production' : 'sandbox'
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# Responsible for determining the redirection to the payment page for a given amount
|
44
|
+
#
|
45
|
+
# @param amount [Integer] the amount **in cents** that the user has to pay
|
46
|
+
# @param callback_url[String] where the user has be redirected after its payment
|
47
|
+
# @param params [Hash] the additional GET parameters sent to the omnipay payment URL. This is where you check wether the arguments expected by your the payment provider are present. In this case, we check :
|
48
|
+
# * +birthdate+ The user's birth date. Mandatory because expected by the provider
|
49
|
+
# * +locale+ The language of the payment page. No required
|
50
|
+
# @return [Array] an array containing the 4 following values :
|
51
|
+
# * +[String]+ 'GET' or 'POST' : the HTTP method to use for redirecting to the payment page
|
52
|
+
# * +[String]+ the absolute URL of the payment page. Example : "https\://my-provider.tld/sc12fdg57df"
|
53
|
+
# * +[Hash]+ the GET or POST parameters to send to the payment page
|
54
|
+
def request_phase(amount, callback_url, params={})
|
55
|
+
# Check our params
|
56
|
+
birthdate = params[:birthdate] && params[:birthdate].to_date
|
57
|
+
raise ArgumentError.new('parameter birthdate must be given') if birthdate.blank?
|
58
|
+
|
59
|
+
locale = params[:locale] || 'en'
|
60
|
+
|
61
|
+
# Build the transaction (this is where the provider-specific code happens)
|
62
|
+
amount_in_dollar = (amount.to_f / 100).round(2)
|
63
|
+
payment_params = build_transaction_for_provider(amount, birthdate, locale, callback_url)
|
64
|
+
|
65
|
+
# Return the redirection details (method, url, params)
|
66
|
+
return ['GET', 'http://my-provider-payment-endpoint.com', payment_params]
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
# Analyze the redirection from the payment provider, to determine if the payment is a success, and its details
|
71
|
+
# @param params [Hash] the GET/POST parameters sent by the payment gateway to the callback url
|
72
|
+
# @return [Hash] the resulting response hash which will be accessible in the application. Must contain the following values :
|
73
|
+
# * *:success* (+Boolean+) | Did the payment occur or not?
|
74
|
+
# * *:transaction_id* (+String+) <i>if successful</i> | The id of the transaction.
|
75
|
+
# * *:error* (+Symbol+) <i>if failed</i> | The reason why the payment was not successful. The available values are :
|
76
|
+
# * _Omnipay::CANCELED_ : The payment didn't occur because of the user.
|
77
|
+
# * _Omnipay::PAYMENT_REFUSED_ : The payment didn't occue because of an error on the gateway's side.
|
78
|
+
# * _Omnipay::INVALID_RESPONSE_ : The response from the gateway cannot be parsed. The payment may or may not have occured.
|
79
|
+
# * *:error_message* (+String+) <i>if failed</i> | A more detailed error message / stack trace, for logging purposes
|
80
|
+
def callback_hash(params)
|
81
|
+
|
82
|
+
transaction_id = params[:transaction_id]
|
83
|
+
transaction = fetch_transaction(transaction_id)
|
84
|
+
|
85
|
+
if !transaction
|
86
|
+
return { :success => false, :error => Omnipay::INVALID_RESPONSE, :error_message => "No transaction found with id #{transaction_id}"}
|
87
|
+
end
|
88
|
+
|
89
|
+
if transaction.success
|
90
|
+
{ :success => true, :transaction_id => transaction_id }
|
91
|
+
else
|
92
|
+
if transaction.canceled
|
93
|
+
{ :success => false, :error => Omnipay::CANCELATION }
|
94
|
+
else
|
95
|
+
{ :success => false, :error => Omnipay::PAYMENT_REFUSED, :error_message => "Transaction #{transaction_id} was not successful : #{transaction.error}" }
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def build_new_transaction(amount, locale)
|
104
|
+
# TODO
|
105
|
+
end
|
106
|
+
|
107
|
+
def fetch_transaction(transaction_id)
|
108
|
+
# TODO
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
112
|
+
|
113
|
+
end
|
114
|
+
end
|
data/lib/omnipay/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omnipay
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ClicRDV
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-08-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -47,16 +47,15 @@ extensions: []
|
|
47
47
|
extra_rdoc_files: []
|
48
48
|
files:
|
49
49
|
- lib/omnipay.rb
|
50
|
-
- lib/omnipay/
|
51
|
-
- lib/omnipay/adapters/mangopay/client.rb
|
52
|
-
- lib/omnipay/adapters/oneclicpay.rb
|
53
|
-
- lib/omnipay/adapters/sample_adapter.rb
|
50
|
+
- lib/omnipay/adapter.rb
|
54
51
|
- lib/omnipay/autosubmit_form.rb
|
55
52
|
- lib/omnipay/configuration.rb
|
56
53
|
- lib/omnipay/gateway.rb
|
57
54
|
- lib/omnipay/gateways.rb
|
55
|
+
- lib/omnipay/helpers.rb
|
58
56
|
- lib/omnipay/middleware.rb
|
59
57
|
- lib/omnipay/railtie.rb
|
58
|
+
- lib/omnipay/sample_adapter.rb
|
60
59
|
- lib/omnipay/version.rb
|
61
60
|
homepage: https://github.com/clicrdv/omnipay
|
62
61
|
licenses: []
|
@@ -77,9 +76,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
76
|
version: '0'
|
78
77
|
requirements: []
|
79
78
|
rubyforge_project: '[none]'
|
80
|
-
rubygems_version: 2.0.
|
79
|
+
rubygems_version: 2.0.3
|
81
80
|
signing_key:
|
82
81
|
specification_version: 4
|
83
82
|
summary: Payment gateway abstraction for rack applications.
|
84
83
|
test_files: []
|
85
|
-
has_rdoc:
|
@@ -1,138 +0,0 @@
|
|
1
|
-
# Omnipay adapter for mangopay
|
2
|
-
# documentation : http://docs.mangopay.com/api-references/
|
3
|
-
#
|
4
|
-
# Configuration :
|
5
|
-
# - client_id:string (mandatory) : client id of your mangopay account
|
6
|
-
# - client_passphrase:string (mandatory) : the passphrase for your account
|
7
|
-
# - wallet_id:string (mandatory) : the wallet to be credited with the payments
|
8
|
-
|
9
|
-
require 'omnipay/adapters/mangopay/client'
|
10
|
-
|
11
|
-
module Omnipay
|
12
|
-
module Adapters
|
13
|
-
|
14
|
-
class Mangopay
|
15
|
-
|
16
|
-
attr_reader :client
|
17
|
-
|
18
|
-
def initialize(config = {})
|
19
|
-
|
20
|
-
raise ArgumentError.new("Missing client_id, client_passphrase, or wallet_id parameter") unless [config[:client_id], config[:client_passphrase], config[:wallet_id]].all?
|
21
|
-
|
22
|
-
@client = Client.new(config[:client_id], config[:client_passphrase], :sandbox => !!config[:sandbox])
|
23
|
-
@wallet_id = config[:wallet_id]
|
24
|
-
|
25
|
-
end
|
26
|
-
|
27
|
-
|
28
|
-
def request_phase(amount, callback_url, params = {})
|
29
|
-
|
30
|
-
transaction_id, redirect_url = create_web_payin(amount, callback_url, params)
|
31
|
-
|
32
|
-
# Generate the path and query parameters from the returned redirect_url string
|
33
|
-
uri = URI(redirect_url)
|
34
|
-
|
35
|
-
return [
|
36
|
-
'GET',
|
37
|
-
"#{uri.scheme}://#{uri.host}#{uri.path}",
|
38
|
-
Rack::Utils.parse_nested_query(uri.query),
|
39
|
-
transaction_id
|
40
|
-
]
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
|
45
|
-
def callback_hash(params)
|
46
|
-
|
47
|
-
transaction_id = params[:transactionId]
|
48
|
-
|
49
|
-
begin
|
50
|
-
response = @client.get "/payins/#{transaction_id}"
|
51
|
-
rescue Mangopay::Client::Error => e
|
52
|
-
return {
|
53
|
-
:success => false,
|
54
|
-
:error => Omnipay::INVALID_RESPONSE,
|
55
|
-
:error_message => "Could not fetch details of transaction #{transaction_id}"
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
# Check if the response is valid
|
60
|
-
if response['code'] != 200
|
61
|
-
return {
|
62
|
-
:success => false,
|
63
|
-
:error => Omnipay::INVALID_RESPONSE
|
64
|
-
}
|
65
|
-
end
|
66
|
-
|
67
|
-
|
68
|
-
# Successful transaction
|
69
|
-
if response['Status'] == 'SUCCEEDED'
|
70
|
-
{
|
71
|
-
:success => true,
|
72
|
-
:amount => response['DebitedFunds']['Amount'],
|
73
|
-
:transaction_id => transaction_id
|
74
|
-
}
|
75
|
-
else
|
76
|
-
|
77
|
-
# Cancelation
|
78
|
-
if ['101001', '101002'].include? response['ResultCode']
|
79
|
-
{
|
80
|
-
:success => false,
|
81
|
-
:error => Omnipay::CANCELATION
|
82
|
-
}
|
83
|
-
else
|
84
|
-
{
|
85
|
-
:success => false,
|
86
|
-
:error => Omnipay::PAYMENT_REFUSED,
|
87
|
-
:error_message => "Refused payment for transaction #{transaction_id}.\nCode : #{response['ResultCode']}\nMessage : #{response['ResultMessage']}"
|
88
|
-
}
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
|
94
|
-
private
|
95
|
-
|
96
|
-
def create_web_payin(amount, callback_url, params)
|
97
|
-
|
98
|
-
# Create a user
|
99
|
-
random_key = "#{Time.now.to_i}-#{(0...3).map { ('a'..'z').to_a[rand(26)] }.join}"
|
100
|
-
user_params = {
|
101
|
-
:Email => "user-#{random_key}@host.tld",
|
102
|
-
:FirstName => "User #{random_key}",
|
103
|
-
:LastName => "User #{random_key}",
|
104
|
-
:Birthday => Time.now.to_i,
|
105
|
-
:Nationality => "FR",
|
106
|
-
:CountryOfResidence => "FR"
|
107
|
-
}
|
108
|
-
|
109
|
-
user_id = (@client.post('/users/natural', user_params))["Id"]
|
110
|
-
|
111
|
-
# Create the web payin
|
112
|
-
payin_params = {
|
113
|
-
:AuthorId => user_id,
|
114
|
-
:DebitedFunds => {
|
115
|
-
:Currency => 'EUR',
|
116
|
-
:Amount => amount
|
117
|
-
},
|
118
|
-
:Fees => {
|
119
|
-
:Currency => 'EUR',
|
120
|
-
:Amount => 0
|
121
|
-
},
|
122
|
-
:CreditedWalletId => @wallet_id,
|
123
|
-
:ReturnURL => callback_url,
|
124
|
-
:Culture => (params[:locale] || 'fr').upcase,
|
125
|
-
:CardType => 'CB_VISA_MASTERCARD',
|
126
|
-
:SecureMode => 'FORCE'
|
127
|
-
}
|
128
|
-
|
129
|
-
payin = @client.post '/payins/card/web', payin_params
|
130
|
-
|
131
|
-
# Return the transaction reference, and the full redirection url
|
132
|
-
return [payin["Id"], payin["RedirectURL"]]
|
133
|
-
end
|
134
|
-
|
135
|
-
end
|
136
|
-
|
137
|
-
end
|
138
|
-
end
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# Client for the mangopay API
|
2
|
-
|
3
|
-
require 'httparty'
|
4
|
-
require 'json'
|
5
|
-
|
6
|
-
module Omnipay
|
7
|
-
module Adapters
|
8
|
-
class Mangopay
|
9
|
-
|
10
|
-
class Client
|
11
|
-
|
12
|
-
class Error < ::StandardError ; end
|
13
|
-
|
14
|
-
include HTTParty
|
15
|
-
|
16
|
-
format :json
|
17
|
-
|
18
|
-
headers 'Accept' => 'application/json'
|
19
|
-
headers 'Content-Type' => 'application/json'
|
20
|
-
|
21
|
-
def initialize(key, secret, opts = {})
|
22
|
-
@key = key
|
23
|
-
@secret = secret
|
24
|
-
|
25
|
-
if opts[:sandbox]
|
26
|
-
@base_uri = 'https://api.sandbox.mangopay.com/v2'
|
27
|
-
else
|
28
|
-
@base_uri = 'https://api.mangopay.com/v2'
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def get(path)
|
33
|
-
response = self.class.get "/#{@key}#{path}",
|
34
|
-
:basic_auth => {:username => @key, :password => @secret},
|
35
|
-
:base_uri => @base_uri
|
36
|
-
|
37
|
-
check_errors(response)
|
38
|
-
|
39
|
-
response.parsed_response.merge("code" => response.code)
|
40
|
-
end
|
41
|
-
|
42
|
-
def post(path, params = {})
|
43
|
-
response = self.class.post "/#{@key}#{path}",
|
44
|
-
:body => params.to_json,
|
45
|
-
:basic_auth => {:username => @key, :password => @secret},
|
46
|
-
:base_uri => @base_uri
|
47
|
-
|
48
|
-
check_errors(response)
|
49
|
-
|
50
|
-
response.parsed_response.merge("code" => response.code)
|
51
|
-
end
|
52
|
-
|
53
|
-
|
54
|
-
private
|
55
|
-
|
56
|
-
def check_errors(response)
|
57
|
-
# nocommit, log the request :/
|
58
|
-
if response.code != 200
|
59
|
-
error_message = response.parsed_response.merge(
|
60
|
-
"code" => response.code
|
61
|
-
)
|
62
|
-
|
63
|
-
raise Error, error_message.inspect
|
64
|
-
end
|
65
|
-
end
|
66
|
-
|
67
|
-
end
|
68
|
-
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
@@ -1,132 +0,0 @@
|
|
1
|
-
# Omnipay adapter for oneclicpay
|
2
|
-
# documentation : docs.oneclicpay.com
|
3
|
-
#
|
4
|
-
# Configuration :
|
5
|
-
# - tpe_id:string (mandatory) : serial number of your virutal payment terminal
|
6
|
-
# - secret_key:string (mandatory) : security key for your account
|
7
|
-
# - sandbox:boolean (default: false) : use the sandbox or the production environment
|
8
|
-
|
9
|
-
require 'base64'
|
10
|
-
require 'digest'
|
11
|
-
require 'httparty'
|
12
|
-
|
13
|
-
module Omnipay
|
14
|
-
module Adapters
|
15
|
-
|
16
|
-
class Oneclicpay
|
17
|
-
|
18
|
-
HTTP_METHOD = 'POST'
|
19
|
-
|
20
|
-
REDIRECT_URLS = {
|
21
|
-
:sandbox => 'https://secure.homologation.oneclicpay.com',
|
22
|
-
:production => 'https://secure.oneclicpay.com'
|
23
|
-
}
|
24
|
-
|
25
|
-
VALIDATION_URLS = {
|
26
|
-
:sandbox => 'https://secure.homologation.oneclicpay.com:60000',
|
27
|
-
:production => 'https://secure.oneclicpay.com:60000'
|
28
|
-
}
|
29
|
-
|
30
|
-
def initialize(config = {})
|
31
|
-
@tpe_id = config[:tpe_id]
|
32
|
-
@secret_key = config[:secret_key]
|
33
|
-
@is_sandbox = config[:sandbox]
|
34
|
-
|
35
|
-
raise ArgumentError.new("Missing tpe_id or secret_key parameter") unless [@tpe_id, @secret_key].all?
|
36
|
-
end
|
37
|
-
|
38
|
-
|
39
|
-
def request_phase(amount, callback_url, params={})
|
40
|
-
product_name = params[:title] || ''
|
41
|
-
transaction_id = params[:transaction_id] || random_transaction_id
|
42
|
-
locale = params[:locale] || 'fr'
|
43
|
-
|
44
|
-
[
|
45
|
-
HTTP_METHOD,
|
46
|
-
redirect_url,
|
47
|
-
redirect_params_for(amount, product_name, transaction_id, locale, callback_url),
|
48
|
-
transaction_id
|
49
|
-
]
|
50
|
-
end
|
51
|
-
|
52
|
-
|
53
|
-
def callback_hash(params)
|
54
|
-
|
55
|
-
if params[:result] == "NOK" && params[:reason] == "Abandon de la transaction."
|
56
|
-
return { :success => false, :error => Omnipay::CANCELATION }
|
57
|
-
end
|
58
|
-
|
59
|
-
|
60
|
-
if params[:result] == "OK"
|
61
|
-
|
62
|
-
# Validate the response via the API
|
63
|
-
transaction_id = params[:transactionId]
|
64
|
-
amount = get_transaction_amount(transaction_id)
|
65
|
-
|
66
|
-
if amount
|
67
|
-
{ :success => true, :amount => amount, :transaction_id => transaction_id }
|
68
|
-
else
|
69
|
-
{ :success => false, :error => Omnipay::INVALID_RESPONSE, :error_message => "Could not fetch the amount of the transaction #{transaction_id}" }
|
70
|
-
end
|
71
|
-
|
72
|
-
|
73
|
-
elsif params[:result] == "NOK"
|
74
|
-
{ :success => false, :error => Omnipay::PAYMENT_REFUSED, :error_message => params[:reason] }
|
75
|
-
|
76
|
-
else
|
77
|
-
{ :success => false, :error => Omnipay::INVALID_RESPONSE, :error_message => "No :result key in the params #{params.inspect}" }
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
|
82
|
-
private
|
83
|
-
|
84
|
-
def redirect_params_for(amount, product_name, transaction_id, locale, callback_url)
|
85
|
-
{
|
86
|
-
:montant => (amount.to_f/100).to_s,
|
87
|
-
:idTPE => @tpe_id,
|
88
|
-
:idTransaction => transaction_id,
|
89
|
-
:devise => 'EUR',
|
90
|
-
:lang => locale,
|
91
|
-
:nom_produit => product_name,
|
92
|
-
:urlRetourOK => callback_url,
|
93
|
-
:urlRetourNOK => callback_url
|
94
|
-
}.tap{|params|
|
95
|
-
params[:sec] = signature(params)
|
96
|
-
}
|
97
|
-
end
|
98
|
-
|
99
|
-
def random_transaction_id
|
100
|
-
"#{Time.now.to_i}-#{@tpe_id}-#{random_token}"
|
101
|
-
end
|
102
|
-
|
103
|
-
def random_token
|
104
|
-
(0...3).map { ('a'..'z').to_a[rand(26)] }.join
|
105
|
-
end
|
106
|
-
|
107
|
-
def signature(params)
|
108
|
-
to_sign = (params.values + [@secret_key]).join('|')
|
109
|
-
Digest::SHA512.hexdigest(Base64.encode64(to_sign).gsub(/\n/, ''))
|
110
|
-
end
|
111
|
-
|
112
|
-
def get_transaction_amount(transaction_id)
|
113
|
-
response = HTTParty.post(
|
114
|
-
"#{validation_url}/rest/payment/find?serialNumber=#{@tpe_id}&key=#{@secret_key}&transactionRef=#{transaction_id}",
|
115
|
-
:headers => {'content-type' => "application/x-www-form-urlencoded"} # For ruby 1.8.7
|
116
|
-
)
|
117
|
-
|
118
|
-
(response.parsed_response["transaction"][0]["ok"] != 0) && response.parsed_response["transaction"][0]["amount"]
|
119
|
-
end
|
120
|
-
|
121
|
-
def redirect_url
|
122
|
-
@is_sandbox ? REDIRECT_URLS[:sandbox] : REDIRECT_URLS[:production]
|
123
|
-
end
|
124
|
-
|
125
|
-
def validation_url
|
126
|
-
@is_sandbox ? VALIDATION_URLS[:sandbox] : VALIDATION_URLS[:production]
|
127
|
-
end
|
128
|
-
|
129
|
-
end
|
130
|
-
|
131
|
-
end
|
132
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
module Omnipay
|
2
|
-
module Adapters
|
3
|
-
|
4
|
-
# This is a sample Omnipay adapter implementation for a fictive payment gateway. You can use it as a boilerplate to develop your own.
|
5
|
-
#
|
6
|
-
# This adapter will take two arguments in its configuration :
|
7
|
-
# * *api_key* [String - mandatory] : given by the payment gateway, used for authenticating to their API
|
8
|
-
# * *sandbox* [Boolean - optional] : whether the payment should be done in a sandbox or with the real payment page
|
9
|
-
# It may then be initialized like this in a rack application
|
10
|
-
# use Omnipay::Gateway,
|
11
|
-
# :uid => 'my-gateway',
|
12
|
-
# :adapter => Omnipay::Adapters::SampleAdapter
|
13
|
-
# :config => {
|
14
|
-
# :api_key => 'my-api-key',
|
15
|
-
# :sandbox => (ENV['RACK_ENV'] != 'production')
|
16
|
-
# }
|
17
|
-
|
18
|
-
class SampleAdapter
|
19
|
-
|
20
|
-
# The adapters initialization
|
21
|
-
# @param callback_url [String] the absolute callback URL the user should be redirected to after the payment. Will be generated by Omnipay. Example value : "http\://\www\.my-host.tld/pay/my-gateway/callback"
|
22
|
-
# @param config [Hash] the adapter's configuration as defined in the application's omnipay config file. For the example above in a dev environment, the value would be {:api_key => 'my-api-key', :sandbox => true}. It is up to you to decide which fields are mandatory, and to both check them in this initializer and specify them in your documentation. In this case :
|
23
|
-
# * *:api_key* [String] (mandatory) : the API key given for your fictive gateway's account
|
24
|
-
# * *:sandbox* [Boolean] (optional) : whether to use a sandboxed payment page, or a real one. Defaults to true
|
25
|
-
# @return [SampleAdapter]
|
26
|
-
def initialize(config = {})
|
27
|
-
@api_key = config[:api_key]
|
28
|
-
raise ArgumentError.new("Missing api_key") unless @api_key
|
29
|
-
|
30
|
-
@mode = (config[:sandbox] == false) ? 'production' : 'sandbox'
|
31
|
-
end
|
32
|
-
|
33
|
-
|
34
|
-
# Responsible for determining the redirection to the payment page for a given amount
|
35
|
-
#
|
36
|
-
# @param amount [Integer] the amount **in cents** that the user has to pay
|
37
|
-
# @param params [Hash] the GET parameters sent to the omnipay payment URL. Can be used to specify transaction-specific variables (the locale to use, the payment page's title, ...). Please use the following keys if you need to, for consistency among adapters :
|
38
|
-
# * *locale* The ISO 639-1 locale code for the payment page
|
39
|
-
# * *title* The title to display on the payment page
|
40
|
-
# * *transaction_id* The transaction id to use, if you want the user to be able to force it
|
41
|
-
# @return [Array] an array containing the 4 following values :
|
42
|
-
# * +[String]+ 'GET' or 'POST' : the HTTP method to use for redirecting to the payment page
|
43
|
-
# * +[String]+ the absolute URL of the payment page. Example : "https\://my-provider.tld/sc12fdg57df"
|
44
|
-
# * +[Hash]+ the GET or POST parameters to send to the payment page
|
45
|
-
# * +[String]+ the unique transaction_id given by the payment provider for the upcoming payment. Has to be accessible in the callback phase.
|
46
|
-
|
47
|
-
def request_phase(amount, calback_url, params={})
|
48
|
-
amount_in_dollar = amount * 1.0 / 100
|
49
|
-
locale = params[:locale] || 'en'
|
50
|
-
|
51
|
-
transaction = build_new_transaction(amount_in_dollars, callback_url, locale)
|
52
|
-
|
53
|
-
uri = URI(transaction.payment_url)
|
54
|
-
|
55
|
-
method = 'GET'
|
56
|
-
url = "#{uri.scheme}://#{uri.host}#{uri.path}"
|
57
|
-
get_params = Rack::Utils.parse_query(uri.query)
|
58
|
-
transaction_id = transaction.id
|
59
|
-
|
60
|
-
return [
|
61
|
-
method,
|
62
|
-
url,
|
63
|
-
get_params,
|
64
|
-
transaction_id
|
65
|
-
]
|
66
|
-
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
# @param params [Hash] the GET/POST parameters sent by the payment gateway to the callback url
|
71
|
-
# @return [Hash] the resulting response hash which will be accessible in the application. Must contain the following values :
|
72
|
-
# * *:success* (+Boolean+) | Did the payment occur or not?
|
73
|
-
# * *:amount* (+Integer+) <i>if successful</i> | The amount <b>in cents</b> actually payed
|
74
|
-
# * *:transaction_id* (+String+) <i>if successful</i> | The id of the transaction. Must match the one returned in the request phase.
|
75
|
-
# * *:error* (+Symbol+) <i>if failed</i> | The reason why the payment was not successful. The available values are :
|
76
|
-
# * _Omnipay::CANCELED_ : The payment didn't occur because of the user.
|
77
|
-
# * _Omnipay::PAYMENT_REFUSED_ : The payment didn't occue because of an error on the gateway's side.
|
78
|
-
# * _Omnipay::INVALID_RESPONSE_ : The response from the gateway cannot be parsed. The payment may or may not have occured.
|
79
|
-
# * *:error_message* (+String+) <i>if failed</i> | A more detailed error message / stack trace, for logging purposes
|
80
|
-
|
81
|
-
def callback_hash(params)
|
82
|
-
|
83
|
-
transaction_id = params[:transaction_id]
|
84
|
-
transaction = fetch_transaction(transaction_id)
|
85
|
-
|
86
|
-
if !transaction
|
87
|
-
return { :success => false, :error => Omnipay::INVALID_RESPONSE, :error_message => "No transaction found with id #{transaction_id}"}
|
88
|
-
end
|
89
|
-
|
90
|
-
if transaction.success
|
91
|
-
{ :success => true, :amount => (transaction.amount*100).to_i, :transaction_id => transaction_id }
|
92
|
-
else
|
93
|
-
if transaction.canceled
|
94
|
-
{ :success => false, :error => Omnipay::CANCELATION }
|
95
|
-
else
|
96
|
-
{ :success => false, :error => Omnipay::PAYMENT_REFUSED, :error_message => "Transaction #{transaction_id} was not successful : #{transaction.error}" }
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
|
102
|
-
private
|
103
|
-
|
104
|
-
def build_new_transaction(amount, locale)
|
105
|
-
# TODO
|
106
|
-
end
|
107
|
-
|
108
|
-
def fetch_transaction(transaction_id)
|
109
|
-
# TODO
|
110
|
-
end
|
111
|
-
|
112
|
-
end
|
113
|
-
|
114
|
-
end
|
115
|
-
end
|