paycertify 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/paycertify.rb +1 -0
- data/lib/paycertify/gateway.rb +35 -0
- data/lib/paycertify/gateway/attribute_mapping.rb +42 -0
- data/lib/paycertify/gateway/client.rb +55 -0
- data/lib/paycertify/gateway/response.rb +31 -0
- data/lib/paycertify/gateway/transaction.rb +54 -0
- data/lib/paycertify/gateway/validation.rb +174 -0
- data/lib/paycertify/three_ds/callback.rb +0 -2
- data/lib/paycertify/three_ds/client.rb +0 -4
- metadata +11 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7d46ae5ef73791cdde93a470cf6021b0c56317c
|
4
|
+
data.tar.gz: b8ecdc243b35a74967e187817f8f4f177518b63e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df5acf381b3a1f0a80eef90542f4ab79c00128383d5d43b0705c326c6c7deafa8ab9004c7ceec966bef0950963073f8dc1b6e3a104b41a0135c38de5df04228c
|
7
|
+
data.tar.gz: bf269069f26803f15a4d64de3607b7e285bf25dae42f3b17be4f431b582964a0d2276bfe6dd8f0e9d66f489ab15ab6f1d64da4a38e688645b54a43b3ec5dde3a
|
data/lib/paycertify.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative './gateway/attribute_mapping'
|
2
|
+
require_relative './gateway/client'
|
3
|
+
require_relative './gateway/response'
|
4
|
+
require_relative './gateway/transaction'
|
5
|
+
require_relative './gateway/validation'
|
6
|
+
|
7
|
+
require 'json'
|
8
|
+
|
9
|
+
module PayCertify
|
10
|
+
class Gateway
|
11
|
+
|
12
|
+
attr_accessor :client, :attributes
|
13
|
+
|
14
|
+
delegate :api_key, :mode, to: :class
|
15
|
+
|
16
|
+
def initialize(attributes)
|
17
|
+
self.attributes = attributes
|
18
|
+
self.client = PayCertify::Gateway::Client.new(api_key: api_key, mode: mode)
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def process_transaction!
|
23
|
+
transaction = PayCertify::Gateway::Transaction.new(client, attributes)
|
24
|
+
transaction.process
|
25
|
+
end
|
26
|
+
|
27
|
+
class << self
|
28
|
+
cattr_accessor :api_key, :mode
|
29
|
+
|
30
|
+
def configure(&block)
|
31
|
+
yield self if block_given?
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
module AttributeMapping
|
4
|
+
module_function
|
5
|
+
def wrapper_to_gateway
|
6
|
+
{
|
7
|
+
'Amount' => :amount,
|
8
|
+
'Currency' => :currency,
|
9
|
+
'CardNum' => :card_number,
|
10
|
+
'NameOnCard' => :name_on_card,
|
11
|
+
'CVNum' => :cvv,
|
12
|
+
'InvNum' => :transaction_id,
|
13
|
+
'PNRef' => :transaction_id,
|
14
|
+
'Street' => :billing_address,
|
15
|
+
'City' => :billing_city,
|
16
|
+
'State' => :billing_state,
|
17
|
+
'Zip' => :billing_zip,
|
18
|
+
'Country' => :billing_country,
|
19
|
+
'ShippingStreet' => :billing_address,
|
20
|
+
'ShippingCity' => :billing_city,
|
21
|
+
'ShippingState' => :billing_state,
|
22
|
+
'ShippingZip' => :billing_zip,
|
23
|
+
'ShippingCountry' => :billing_country,
|
24
|
+
'MobilePhone' => :phone,
|
25
|
+
'Email' => :email,
|
26
|
+
'Description' => :order_description,
|
27
|
+
'CustomerID' => :customer_id,
|
28
|
+
'ServerID' => :ip,
|
29
|
+
'ExtData' => :metadata
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def expiration_date(transaction)
|
34
|
+
{ 'ExpDate' => [transaction.expiration_month, transaction.expiration_year].join }
|
35
|
+
end
|
36
|
+
|
37
|
+
def transaction_type(transaction)
|
38
|
+
{ 'TransType' => transaction.type.to_s.capitalize }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
|
3
|
+
module PayCertify
|
4
|
+
class Gateway
|
5
|
+
class Client
|
6
|
+
|
7
|
+
attr_accessor :api_key, :mode, :response
|
8
|
+
|
9
|
+
def initialize(api_key:, mode:)
|
10
|
+
self.api_key = api_key
|
11
|
+
self.mode = mode.to_s.to_sym
|
12
|
+
end
|
13
|
+
|
14
|
+
def live?
|
15
|
+
mode.to_sym == :live
|
16
|
+
end
|
17
|
+
|
18
|
+
def api_endpoint
|
19
|
+
@api_endpoint ||= 'https://'+ (live?? 'gateway' : 'demo') +'.paycertify.net'
|
20
|
+
end
|
21
|
+
|
22
|
+
def post(path:, data:)
|
23
|
+
body = data.merge('ApiToken' => api_key)
|
24
|
+
|
25
|
+
response = connection.post do |request|
|
26
|
+
request.url path
|
27
|
+
request.body = body
|
28
|
+
end
|
29
|
+
|
30
|
+
respond_with response
|
31
|
+
end
|
32
|
+
|
33
|
+
def success?
|
34
|
+
response.status < 400
|
35
|
+
end
|
36
|
+
|
37
|
+
def error?
|
38
|
+
!success?
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def connection
|
43
|
+
@connection ||= Faraday.new(url: api_endpoint, ssl: {verify: false}) do |faraday|
|
44
|
+
faraday.request :url_encoded
|
45
|
+
faraday.response :logger
|
46
|
+
faraday.adapter Faraday.default_adapter
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def respond_with(response)
|
51
|
+
self.response = PayCertify::Gateway::Response.new(response)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
class Response < HashWithIndifferentAccess
|
4
|
+
|
5
|
+
APPROVED = '0'.freeze
|
6
|
+
|
7
|
+
attr_accessor :status, :original_body
|
8
|
+
|
9
|
+
def initialize(response)
|
10
|
+
self.status = response.status
|
11
|
+
self.original_body = Hash.from_xml(response.body)['Response']
|
12
|
+
|
13
|
+
super(
|
14
|
+
id: original_body['PNRef'],
|
15
|
+
status: response.status,
|
16
|
+
status_string: original_body['Result'] == APPROVED ? 'approved' : 'declined',
|
17
|
+
message: [original_body['Message'], original_body['Message1'], original_body['Message2']].compact.join(' / '),
|
18
|
+
amount: original_body['Amount'].to_d,
|
19
|
+
auth_code: original_body['AuthCode'],
|
20
|
+
payment_type: original_body['PaymentType'],
|
21
|
+
card_number: original_body['Account'],
|
22
|
+
created_at: DateTime.strptime([original_body['TransDate'], original_body['TransTime']].join, '%m%d%Y%H%M%s')
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def success?
|
27
|
+
self['status_string'] == 'approved'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
class Transaction
|
4
|
+
|
5
|
+
TRANSACTION_PATH = '/ws/encgateway2.asmx/ProcessCreditCard'
|
6
|
+
|
7
|
+
attr_accessor :client, :original_attributes
|
8
|
+
|
9
|
+
attr_accessor :type, :amount, :currency, :card_number, :expiration_month, :expiration_year,
|
10
|
+
:name_on_card, :cvv, :transaction_id, :billing_address, :billing_city, :billing_state, :billing_country, :billing_zip,
|
11
|
+
:shipping_address, :shipping_city, :shipping_state, :shipping_country,
|
12
|
+
:shipping_zip, :email, :phone, :ip, :order_description, :customer_id, :metadata
|
13
|
+
|
14
|
+
def initialize(client, attributes)
|
15
|
+
self.original_attributes = attributes
|
16
|
+
self.client = client
|
17
|
+
|
18
|
+
validation.attributes.each do |key, value|
|
19
|
+
self.send("#{key}=", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def validation
|
24
|
+
@validation ||= PayCertify::Gateway::Validation.new(original_attributes)
|
25
|
+
end
|
26
|
+
|
27
|
+
def process
|
28
|
+
client.post(
|
29
|
+
path: TRANSACTION_PATH,
|
30
|
+
data: attributes_to_gateway_format
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def attributes_to_gateway_format
|
36
|
+
{}.tap do |formatted|
|
37
|
+
attribute_mapping = PayCertify::Gateway::AttributeMapping
|
38
|
+
|
39
|
+
attribute_mapping.wrapper_to_gateway.each do |key, value|
|
40
|
+
[value].flatten.tap do |method_chain|
|
41
|
+
new_value = method_chain.map { |method_name| self.send(method_name) }.join
|
42
|
+
formatted[key] = new_value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
formatted.merge! attribute_mapping.expiration_date(self)
|
47
|
+
formatted.merge! attribute_mapping.transaction_type(self)
|
48
|
+
|
49
|
+
formatted
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module PayCertify
|
4
|
+
class Gateway
|
5
|
+
class Validation
|
6
|
+
|
7
|
+
ATTRIBUTES = [
|
8
|
+
# Mandatory fields
|
9
|
+
{ name: :type, validation: :type_validation, required: true },
|
10
|
+
{ name: :amount, validation: :amount_validation, required: true },
|
11
|
+
{ name: :currency, validation: :currency_validation, required: true },
|
12
|
+
{ name: :card_number, validation: :card_number_validation, required: true },
|
13
|
+
{ name: :expiration_month, validation: :expiration_month_validation, required: true },
|
14
|
+
{ name: :expiration_year, validation: :expiration_year_validation, required: true },
|
15
|
+
{ name: :name_on_card, validation: :no_validation, required: true },
|
16
|
+
{ name: :cvv, validation: :no_validation, required: true },
|
17
|
+
{ name: :transaction_id, validation: :no_validation, required: true },
|
18
|
+
{ name: :billing_city, validation: :no_validation, required: true },
|
19
|
+
{ name: :billing_state, validation: :no_validation, required: true },
|
20
|
+
{ name: :billing_country, validation: :no_validation, required: true },
|
21
|
+
{ name: :billing_zip, validation: :zip_validation, required: true },
|
22
|
+
|
23
|
+
# Optional fields
|
24
|
+
{ name: :billing_address, validation: :no_validation, required: false },
|
25
|
+
{ name: :shipping_address, validation: :no_validation, required: false },
|
26
|
+
{ name: :shipping_city, validation: :no_validation, required: false },
|
27
|
+
{ name: :shipping_state, validation: :no_validation, required: false },
|
28
|
+
{ name: :shipping_country, validation: :no_validation, required: false },
|
29
|
+
{ name: :shipping_zip, validation: :zip_validation, required: false },
|
30
|
+
{ name: :email, validation: :email_validation, required: false },
|
31
|
+
{ name: :phone, validation: :no_validation, required: false },
|
32
|
+
{ name: :ip, validation: :ip_validation, required: false },
|
33
|
+
{ name: :order_description, validation: :no_validation, required: false },
|
34
|
+
{ name: :customer_id, validation: :no_validation, required: false },
|
35
|
+
{ name: :metadata, validation: :no_validation, required: false }
|
36
|
+
]
|
37
|
+
|
38
|
+
ALLOWED_TYPES = %w(sale auth refund void force recurring)
|
39
|
+
ALLOWED_CURRENCIES = %w(USD EUR)
|
40
|
+
CREDIT_CARD_REGEX = /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$/i
|
41
|
+
EMAIL_REGEX = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
|
42
|
+
|
43
|
+
class ValidationError < StandardError; end
|
44
|
+
|
45
|
+
attr_accessor :attributes, :errors
|
46
|
+
|
47
|
+
def initialize(attributes={})
|
48
|
+
self.attributes = attributes
|
49
|
+
|
50
|
+
ATTRIBUTES.each do |attribute|
|
51
|
+
presence_validation(attribute) if attribute[:required]
|
52
|
+
send(attribute[:validation], attribute) if value_for(attribute).present?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
def presence_validation(attribute)
|
58
|
+
if value_for(attribute).blank?
|
59
|
+
raise ValidationError, "Required attribute not present: #{attribute[:name]}."
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def no_validation(_); end
|
64
|
+
|
65
|
+
def type_validation(attribute)
|
66
|
+
unless value_for(attribute).try(:to_s).in?(ALLOWED_TYPES)
|
67
|
+
raise ValidationError, error_message(attribute, "Must be one of #{ALLOWED_TYPES.join(', ')}")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def amount_validation(attribute)
|
72
|
+
set_attribute(attribute, Float(value_for(attribute)))
|
73
|
+
rescue ArgumentError
|
74
|
+
raise ValidationError, error_message(attribute, "Must be a float, integer or decimal")
|
75
|
+
end
|
76
|
+
|
77
|
+
def currency_validation(attribute)
|
78
|
+
set_attribute(attribute, value_for(attribute).upcase)
|
79
|
+
|
80
|
+
unless value_for(attribute).try(:to_s).in?(ALLOWED_CURRENCIES)
|
81
|
+
raise ValidationError, error_message(attribute, "Must be one of #{ALLOWED_CURRENCIES.join(', ')}")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def card_number_validation(attribute)
|
86
|
+
# Non decimal numbers should be stripped to match.
|
87
|
+
set_attribute(attribute, value_for(attribute).gsub(/\D/, ''))
|
88
|
+
|
89
|
+
unless value_for(attribute) =~ CREDIT_CARD_REGEX
|
90
|
+
raise ValidationError, error_message(attribute, "Doesn't validate as a credit card.")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def zip_validation
|
95
|
+
# Check if it's a number
|
96
|
+
set_attribute(attribute, Integer(value_for(attribute)).to_s)
|
97
|
+
|
98
|
+
if value_for(attribute).length > 5
|
99
|
+
# Raise validation error
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def expiration_month_validation(attribute)
|
104
|
+
# if a string, check if length = 2 and smaller than int 12
|
105
|
+
# if int, transform into string with zero pad and check if smaller than int 12
|
106
|
+
integer = Integer(value_for(attribute))
|
107
|
+
|
108
|
+
if integer > 12
|
109
|
+
raise ValidationError, error_message(attribute, "Must be smaller than 12.")
|
110
|
+
end
|
111
|
+
|
112
|
+
set_attribute(attribute, integer.to_s.rjust(2, '0'))
|
113
|
+
|
114
|
+
rescue ArgumentError
|
115
|
+
raise ValidationError, error_message(attribute, "Must be an integer.")
|
116
|
+
end
|
117
|
+
|
118
|
+
def expiration_year_validation(attribute)
|
119
|
+
# if length = 4, strip to 2;
|
120
|
+
# if a string, check if length = 2 and smaller than int 12
|
121
|
+
# if int, transform into string with zero pad and check if smaller than int 12
|
122
|
+
|
123
|
+
|
124
|
+
is_four_digit = if value_for(attribute).is_a?(String)
|
125
|
+
value_for(attribute).length == 4
|
126
|
+
else
|
127
|
+
value_for(attribute) > 999
|
128
|
+
end
|
129
|
+
|
130
|
+
integer_value = Integer(value_for(attribute))
|
131
|
+
|
132
|
+
set_attribute(attribute, integer_value.to_s.last(2))
|
133
|
+
rescue
|
134
|
+
raise ValidationError, error_message(attribute, "Must be a 2 to 4-digit string.")
|
135
|
+
end
|
136
|
+
|
137
|
+
def zip_validation(attribute)
|
138
|
+
set_attribute(attribute, Integer(value_for(attribute)).to_s)
|
139
|
+
|
140
|
+
unless value_for(attribute).length == 5
|
141
|
+
raise ValidationError, error_message(attribute, "Must be a 5-digit string that can evaluate to a number.")
|
142
|
+
end
|
143
|
+
|
144
|
+
rescue
|
145
|
+
raise ValidationError, error_message(attribute, "Must be a 5-digit string that can evaluate to a number.")
|
146
|
+
end
|
147
|
+
|
148
|
+
def email_validation(attribute)
|
149
|
+
unless value_for(attribute) =~ EMAIL_REGEX
|
150
|
+
raise ValidationError, error_message(attribute, "Doesn't validate as an email.")
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def ip_validation(attribute)
|
155
|
+
IPAddr.new(value_for(attribute))
|
156
|
+
rescue IPAddr::InvalidAddressError
|
157
|
+
raise ValidationError, error_message(attribute, "Doesn't validate as an IP.")
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
def value_for(attribute)
|
162
|
+
attributes[attribute[:name]]
|
163
|
+
end
|
164
|
+
|
165
|
+
def set_attribute(attribute, value)
|
166
|
+
self.attributes[attribute[:name]] = value
|
167
|
+
end
|
168
|
+
|
169
|
+
def error_message(attribute, message)
|
170
|
+
"Attribute #{attribute[:name]} passed as: #{value_for(attribute)}. #{message}"
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paycertify
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- PayCertify Engineering Team
|
@@ -16,28 +16,28 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0'
|
19
|
+
version: '5.0'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0'
|
26
|
+
version: '5.0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: faraday
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.11.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 0.11.0
|
41
41
|
description: Interact with the Gateway, 3DS, Kount, and FraudPortal
|
42
42
|
email: engineering@paycertify.com
|
43
43
|
executables: []
|
@@ -45,6 +45,12 @@ extensions: []
|
|
45
45
|
extra_rdoc_files: []
|
46
46
|
files:
|
47
47
|
- lib/paycertify.rb
|
48
|
+
- lib/paycertify/gateway.rb
|
49
|
+
- lib/paycertify/gateway/attribute_mapping.rb
|
50
|
+
- lib/paycertify/gateway/client.rb
|
51
|
+
- lib/paycertify/gateway/response.rb
|
52
|
+
- lib/paycertify/gateway/transaction.rb
|
53
|
+
- lib/paycertify/gateway/validation.rb
|
48
54
|
- lib/paycertify/three_ds.rb
|
49
55
|
- lib/paycertify/three_ds/callback.rb
|
50
56
|
- lib/paycertify/three_ds/client.rb
|