paycertify 0.0.5 → 0.0.6
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/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
|