paycertify 0.0.7 → 0.1.0
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 +2 -0
- data/lib/paycertify/confirmation.rb +94 -0
- data/lib/paycertify/gateway.rb +17 -17
- data/lib/paycertify/gateway/attribute_mapping.rb +49 -6
- data/lib/paycertify/gateway/base.rb +8 -0
- data/lib/paycertify/gateway/base/resource.rb +87 -0
- data/lib/paycertify/gateway/base/validation.rb +105 -0
- data/lib/paycertify/gateway/charge.rb +58 -0
- data/lib/paycertify/gateway/client.rb +11 -1
- data/lib/paycertify/gateway/credit_card.rb +65 -0
- data/lib/paycertify/gateway/customer.rb +60 -0
- data/lib/paycertify/gateway/response.rb +18 -15
- data/lib/paycertify/gateway/transaction.rb +97 -35
- data/lib/paycertify/insurance.rb +118 -0
- data/lib/paycertify/three_ds.rb +1 -1
- data/lib/paycertify/three_ds/callback.rb +8 -0
- data/lib/paycertify/three_ds/client.rb +5 -0
- data/lib/paycertify/three_ds/form.rb +24 -26
- metadata +23 -2
- data/lib/paycertify/gateway/validation.rb +0 -174
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4853d8c8ab842f2530d63b7c7880e51b0131c6cb
|
4
|
+
data.tar.gz: 65aa6d612b3c3c7154595a7d59cb5f31278f4fb9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: accec71a0359c602880883d965b315fd65f2bb9189719ad9003e33145f3ec079c78983e6d864f6ead27f453edb42dfb4ef34c998ca416093601f3e44b7a1241e
|
7
|
+
data.tar.gz: df636c0cd6d851677d7fc2e4428be57c5b9b426c2ad1de1f86122daf6756fd9368628b3f890ca1ae4dcd6be22aa63316e0fa3a77a5976cc360e6f985bb6618dd
|
data/lib/paycertify.rb
CHANGED
@@ -0,0 +1,94 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Confirmation
|
3
|
+
|
4
|
+
class NoCredentialsError < StandardError; end
|
5
|
+
|
6
|
+
API_ENDPOINT = 'https://www.paycertify.com/'
|
7
|
+
|
8
|
+
MANDATORY_FIELDS = [
|
9
|
+
:transaction_id, :cc_last_four_digits, :name, :email,
|
10
|
+
:phone, :amount, :currency, :payment_gateway
|
11
|
+
]
|
12
|
+
|
13
|
+
OPTIONAL_FIELDS = [
|
14
|
+
:status, :transaction_date, :order_description, :card_type, :name_on_card,
|
15
|
+
:address, :city, :zip, :state, :country, :confirmation_type, :fraud_score_processing, :scheduled_messages,
|
16
|
+
:thank_you_page_url, :metadata
|
17
|
+
]
|
18
|
+
|
19
|
+
attr_accessor :attributes, :errors, :response
|
20
|
+
|
21
|
+
delegate :api_key, to: :class
|
22
|
+
|
23
|
+
def initialize(attributes)
|
24
|
+
raise NoCredentialsError, 'No api key provided.' unless api_key.present?
|
25
|
+
|
26
|
+
self.attributes = HashWithIndifferentAccess.new(attributes)
|
27
|
+
self.errors = {}
|
28
|
+
|
29
|
+
validate!
|
30
|
+
end
|
31
|
+
|
32
|
+
def success?
|
33
|
+
response.success?
|
34
|
+
end
|
35
|
+
|
36
|
+
def start!
|
37
|
+
data = attributes.slice *[MANDATORY_FIELDS + OPTIONAL_FIELDS].flatten
|
38
|
+
|
39
|
+
api_response = connection.post do |request|
|
40
|
+
request.url path_for('merchant/transactions')
|
41
|
+
request.headers['Content-Type'] = 'application/json'
|
42
|
+
request.headers['PAYCERTIFYKEY'] = api_key
|
43
|
+
request.body = JSON.generate(data)
|
44
|
+
end
|
45
|
+
|
46
|
+
self.response = Response.new(api_response)
|
47
|
+
self.errors = errors.merge(response) unless response.success?
|
48
|
+
|
49
|
+
response
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
attr_accessor :api_key
|
54
|
+
|
55
|
+
def configure(&block)
|
56
|
+
yield self if block_given?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Response < HashWithIndifferentAccess
|
61
|
+
attr_accessor :original_response
|
62
|
+
|
63
|
+
def initialize(response)
|
64
|
+
self.original_response = response
|
65
|
+
super JSON.parse(response.body)
|
66
|
+
end
|
67
|
+
|
68
|
+
def success?
|
69
|
+
original_response.status < 400
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def validate!
|
75
|
+
MANDATORY_FIELDS.each do |field|
|
76
|
+
unless attributes[field].present?
|
77
|
+
self.errors[field] = "Required attribute not present"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def connection
|
83
|
+
@connection ||= Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
|
84
|
+
faraday.request :url_encoded
|
85
|
+
faraday.response :logger
|
86
|
+
faraday.adapter Faraday.default_adapter
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def path_for(path)
|
91
|
+
return "api/v1/" + path
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
data/lib/paycertify/gateway.rb
CHANGED
@@ -1,34 +1,34 @@
|
|
1
1
|
require_relative './gateway/attribute_mapping'
|
2
|
+
require_relative './gateway/base'
|
3
|
+
require_relative './gateway/charge'
|
2
4
|
require_relative './gateway/client'
|
5
|
+
require_relative './gateway/credit_card'
|
6
|
+
require_relative './gateway/customer'
|
3
7
|
require_relative './gateway/response'
|
4
8
|
require_relative './gateway/transaction'
|
5
|
-
require_relative './gateway/validation'
|
6
9
|
|
7
10
|
require 'json'
|
8
11
|
|
9
12
|
module PayCertify
|
10
13
|
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
|
14
|
+
CREDENTIALS_PATH = '/ws/encgateway2.asmx/GetCredential'
|
26
15
|
|
27
16
|
class << self
|
28
|
-
|
17
|
+
attr_accessor :api_key, :mode, :vendor
|
29
18
|
|
30
19
|
def configure(&block)
|
31
20
|
yield self if block_given?
|
21
|
+
|
22
|
+
client = PayCertify::Gateway::Client.new(api_key: api_key, mode: mode)
|
23
|
+
response = client.get(path: CREDENTIALS_PATH)
|
24
|
+
|
25
|
+
self.vendor = response['response']['vendor']
|
26
|
+
|
27
|
+
return {
|
28
|
+
api_key: api_key,
|
29
|
+
mode: mode,
|
30
|
+
vendor: vendor
|
31
|
+
}
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -2,7 +2,7 @@ module PayCertify
|
|
2
2
|
class Gateway
|
3
3
|
module AttributeMapping
|
4
4
|
module_function
|
5
|
-
def
|
5
|
+
def transaction
|
6
6
|
{
|
7
7
|
'Amount' => :amount,
|
8
8
|
'Currency' => :currency,
|
@@ -25,8 +25,40 @@ module PayCertify
|
|
25
25
|
'Email' => :email,
|
26
26
|
'Description' => :order_description,
|
27
27
|
'CustomerID' => :customer_id,
|
28
|
-
'ServerID' => :ip
|
29
|
-
|
28
|
+
'ServerID' => :ip
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def customer
|
33
|
+
{
|
34
|
+
'CustomerID' => :app_customer_id,
|
35
|
+
'CustomerKey' => :customer_id,
|
36
|
+
'CustomerName' => :name,
|
37
|
+
'Street1' => :address,
|
38
|
+
'City' => :city,
|
39
|
+
'StateID' => :state,
|
40
|
+
'Zip' => :zip,
|
41
|
+
'MobilePhone' => :phone,
|
42
|
+
'Fax' => :fax,
|
43
|
+
'Email' => :email,
|
44
|
+
'Status' => :status
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def credit_card
|
49
|
+
{
|
50
|
+
'CardNum' => :card_number,
|
51
|
+
'NameOnCard' => :name_on_card,
|
52
|
+
'CustomerKey' => :customer_id,
|
53
|
+
'PostalCode' => :zip
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def charge
|
58
|
+
{
|
59
|
+
'CardToken' => :credit_card_id,
|
60
|
+
'Amount' => :amount,
|
61
|
+
'PNRef' => :transaction_id
|
30
62
|
}
|
31
63
|
end
|
32
64
|
|
@@ -34,9 +66,20 @@ module PayCertify
|
|
34
66
|
{ 'ExpDate' => [transaction.expiration_month, transaction.expiration_year].join }
|
35
67
|
end
|
36
68
|
|
37
|
-
def
|
38
|
-
|
69
|
+
def type(object)
|
70
|
+
case object.class.name
|
71
|
+
when /.*Transaction.*/, /.*Charge.*/
|
72
|
+
{ 'TransType' => object.type.to_s.capitalize }
|
73
|
+
when /.*Customer.*/
|
74
|
+
{ 'TransType' => object.type.to_s.upcase }
|
75
|
+
else
|
76
|
+
{}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def status(customer)
|
81
|
+
object.status.to_s.upcase
|
39
82
|
end
|
40
83
|
end
|
41
84
|
end
|
42
|
-
end
|
85
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
module Base
|
4
|
+
class Resource
|
5
|
+
attr_accessor :client, :original_attributes, :response, :errors
|
6
|
+
|
7
|
+
delegate :api_key, :mode, to: PayCertify::Gateway
|
8
|
+
|
9
|
+
def initialize(attributes)
|
10
|
+
self.original_attributes = attributes
|
11
|
+
self.client = PayCertify::Gateway::Client.new(api_key: api_key, mode: mode)
|
12
|
+
|
13
|
+
if validatable?
|
14
|
+
# Validate + attribute assignment
|
15
|
+
validation.attributes.each do |key, value|
|
16
|
+
self.send("#{key}=", value)
|
17
|
+
end
|
18
|
+
|
19
|
+
self.errors = validation.errors
|
20
|
+
else
|
21
|
+
# Attribute assignment only
|
22
|
+
self.class.const_get('ATTRIBUTES').each do |key|
|
23
|
+
self.send("#{key}=", attributes[key])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def success?
|
29
|
+
errors.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def validatable?
|
33
|
+
self.class.const_get('Validation')
|
34
|
+
true
|
35
|
+
rescue NameError
|
36
|
+
false
|
37
|
+
end
|
38
|
+
|
39
|
+
def validation
|
40
|
+
@validation ||= self.class.const_get('Validation').new(original_attributes)
|
41
|
+
end
|
42
|
+
|
43
|
+
def attributes
|
44
|
+
{}.tap do |attributes|
|
45
|
+
self.class.const_get('ATTRIBUTES').each do |attribute|
|
46
|
+
value = self.send(attribute)
|
47
|
+
attributes[attribute] = value if value.present?
|
48
|
+
end
|
49
|
+
|
50
|
+
if response.present?
|
51
|
+
attributes['gateway_response'] = response
|
52
|
+
end
|
53
|
+
|
54
|
+
attributes
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_json
|
59
|
+
JSON.generate(attributes)
|
60
|
+
end
|
61
|
+
|
62
|
+
def save!
|
63
|
+
self.response = client.post(
|
64
|
+
path: self.class.const_get('API_ENDPOINT'),
|
65
|
+
data: attributes_to_gateway_format
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def attributes_to_gateway_format
|
70
|
+
{}.tap do |formatted|
|
71
|
+
attribute_mapping = PayCertify::Gateway::AttributeMapping
|
72
|
+
mapping_name = self.class.name.underscore.split('/').last
|
73
|
+
|
74
|
+
attribute_mapping.send(mapping_name).each do |key, value|
|
75
|
+
[value].flatten.tap do |method_chain|
|
76
|
+
new_value = method_chain.map { |method_name| self.send(method_name) }.join
|
77
|
+
formatted[key] = new_value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
formatted
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
module Base
|
4
|
+
class Validation
|
5
|
+
|
6
|
+
EMAIL_REGEX = /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
|
7
|
+
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
|
8
|
+
|
9
|
+
attr_accessor :attributes, :errors
|
10
|
+
|
11
|
+
def initialize(attributes)
|
12
|
+
self.attributes = attributes
|
13
|
+
self.errors = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def no_validation(_); end
|
17
|
+
|
18
|
+
def presence_validation(attribute)
|
19
|
+
if value_for(attribute).blank?
|
20
|
+
add_error(attribute, "Required attribute not present")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def email_validation(attribute)
|
25
|
+
unless value_for(attribute) =~ EMAIL_REGEX
|
26
|
+
add_error(attribute, "Doesn't validate as an email.")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def zip_validation(attribute)
|
31
|
+
set_attribute(attribute, Integer(value_for(attribute)).to_s)
|
32
|
+
|
33
|
+
unless value_for(attribute).length == 5
|
34
|
+
add_error(attribute, "Must be a 5-digit string that can evaluate to a number.")
|
35
|
+
end
|
36
|
+
|
37
|
+
rescue
|
38
|
+
add_error(attribute, "Must be a 5-digit string that can evaluate to a number.")
|
39
|
+
end
|
40
|
+
|
41
|
+
def card_number_validation(attribute)
|
42
|
+
# Non decimal numbers should be stripped to match.
|
43
|
+
set_attribute(attribute, value_for(attribute).gsub(/\D/, ''))
|
44
|
+
|
45
|
+
unless value_for(attribute) =~ CREDIT_CARD_REGEX
|
46
|
+
add_error(attribute, "Doesn't validate as a credit card.")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def expiration_month_validation(attribute)
|
51
|
+
# if a string, check if length = 2 and smaller than int 12
|
52
|
+
# if int, transform into string with zero pad and check if smaller than int 12
|
53
|
+
integer = Integer(value_for(attribute))
|
54
|
+
|
55
|
+
if integer > 12
|
56
|
+
add_error(attribute, "Must be smaller than 12.")
|
57
|
+
end
|
58
|
+
|
59
|
+
set_attribute(attribute, integer.to_s.rjust(2, '0'))
|
60
|
+
|
61
|
+
rescue ArgumentError
|
62
|
+
add_error(attribute, "Must be an integer.")
|
63
|
+
end
|
64
|
+
|
65
|
+
def expiration_year_validation(attribute)
|
66
|
+
# if length = 4, strip to 2;
|
67
|
+
# if a string, check if length = 2 and smaller than int 12
|
68
|
+
# if int, transform into string with zero pad and check if smaller than int 12
|
69
|
+
|
70
|
+
|
71
|
+
is_four_digit = if value_for(attribute).is_a?(String)
|
72
|
+
value_for(attribute).length == 4
|
73
|
+
else
|
74
|
+
value_for(attribute) > 999
|
75
|
+
end
|
76
|
+
|
77
|
+
integer_value = Integer(value_for(attribute))
|
78
|
+
|
79
|
+
set_attribute(attribute, integer_value.to_s.last(2))
|
80
|
+
rescue
|
81
|
+
add_error(attribute, "Must be a 2 to 4-digit string.")
|
82
|
+
end
|
83
|
+
|
84
|
+
def amount_validation(attribute)
|
85
|
+
set_attribute(attribute, Float(value_for(attribute)))
|
86
|
+
rescue ArgumentError
|
87
|
+
add_error(attribute, "Must be a float, integer or decimal")
|
88
|
+
end
|
89
|
+
|
90
|
+
def value_for(attribute)
|
91
|
+
attributes[attribute[:name]]
|
92
|
+
end
|
93
|
+
|
94
|
+
def set_attribute(attribute, value)
|
95
|
+
self.attributes[attribute[:name]] = value
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_error(attribute, message)
|
99
|
+
self.errors[attribute[:name]] ||= []
|
100
|
+
self.errors[attribute[:name]] << message
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
class Charge < PayCertify::Gateway::Base::Resource
|
4
|
+
|
5
|
+
API_ENDPOINT = '/ws/cardsafe.asmx/ProcessStoredCard'
|
6
|
+
|
7
|
+
ATTRIBUTES = [
|
8
|
+
:transaction_id, :app_transaction_id, :type, :amount,
|
9
|
+
:credit_card_id, :gateway_response
|
10
|
+
]
|
11
|
+
|
12
|
+
attr_accessor *ATTRIBUTES
|
13
|
+
|
14
|
+
alias :execute! :save!
|
15
|
+
|
16
|
+
def type
|
17
|
+
'sale'
|
18
|
+
end
|
19
|
+
|
20
|
+
def success?
|
21
|
+
response['response']['transaction_result']['result'] == '0'
|
22
|
+
end
|
23
|
+
|
24
|
+
def save!
|
25
|
+
super
|
26
|
+
self.transaction_id = response['response']['transaction_result']['pn_ref']
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes_to_gateway_format
|
31
|
+
formatted = super
|
32
|
+
attribute_mapping = PayCertify::Gateway::AttributeMapping
|
33
|
+
|
34
|
+
formatted.merge! attribute_mapping.type(self)
|
35
|
+
formatted.merge!({'TokenMode' => 'DEFAULT'})
|
36
|
+
|
37
|
+
formatted
|
38
|
+
end
|
39
|
+
|
40
|
+
class Validation < PayCertify::Gateway::Base::Validation
|
41
|
+
ALL_VALIDATIONS = [
|
42
|
+
{ name: :credit_card_id, validation: :no_validation, required: true },
|
43
|
+
{ name: :amount, validation: :amount_validation, required: true },
|
44
|
+
{ name: :transaction_id, validation: :no_validation, required: true },
|
45
|
+
]
|
46
|
+
|
47
|
+
def initialize(attributes={})
|
48
|
+
super(attributes)
|
49
|
+
|
50
|
+
ALL_VALIDATIONS.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
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -19,8 +19,14 @@ module PayCertify
|
|
19
19
|
@api_endpoint ||= 'https://'+ (live?? 'gateway' : 'demo') +'.paycertify.net'
|
20
20
|
end
|
21
21
|
|
22
|
+
def get(path:, data: {})
|
23
|
+
data.merge!(token_payload)
|
24
|
+
response = connection.get(path, data)
|
25
|
+
respond_with response
|
26
|
+
end
|
27
|
+
|
22
28
|
def post(path:, data:)
|
23
|
-
body = data.merge(
|
29
|
+
body = data.merge(token_payload)
|
24
30
|
|
25
31
|
response = connection.post do |request|
|
26
32
|
request.url path
|
@@ -47,6 +53,10 @@ module PayCertify
|
|
47
53
|
end
|
48
54
|
end
|
49
55
|
|
56
|
+
def token_payload
|
57
|
+
{ 'ApiToken' => api_key }
|
58
|
+
end
|
59
|
+
|
50
60
|
def respond_with(response)
|
51
61
|
self.response = PayCertify::Gateway::Response.new(response)
|
52
62
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
class CreditCard < PayCertify::Gateway::Base::Resource
|
4
|
+
|
5
|
+
API_ENDPOINT = '/ws/cardsafe.asmx/StoreCard'
|
6
|
+
SAFE_CARD_REGEX = /\<CardSafeToken>(.*)<\/CardSafeToken>/
|
7
|
+
|
8
|
+
ATTRIBUTES = [
|
9
|
+
:credit_card_id, :card_number, :expiration_month, :expiration_year,
|
10
|
+
:customer_id, :name_on_card, :zip
|
11
|
+
]
|
12
|
+
|
13
|
+
attr_accessor *ATTRIBUTES
|
14
|
+
|
15
|
+
def success?
|
16
|
+
super && response['response']['result'] == '0'
|
17
|
+
end
|
18
|
+
|
19
|
+
def save!
|
20
|
+
super
|
21
|
+
self.credit_card_id = get_credit_card_id
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def attributes_to_gateway_format
|
26
|
+
formatted = super
|
27
|
+
attribute_mapping = PayCertify::Gateway::AttributeMapping
|
28
|
+
|
29
|
+
formatted.merge! attribute_mapping.expiration_date(self)
|
30
|
+
formatted.merge!({'TokenMode' => 'DEFAULT'})
|
31
|
+
|
32
|
+
formatted
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_credit_card_id
|
36
|
+
response['response']['ext_data'].match(SAFE_CARD_REGEX)
|
37
|
+
credit_card_id = $1
|
38
|
+
credit_card_id.presence || response['response']['ext_data']['safe_card_token']
|
39
|
+
end
|
40
|
+
|
41
|
+
class Validation < PayCertify::Gateway::Base::Validation
|
42
|
+
ALL_VALIDATIONS = [
|
43
|
+
# Mandatory fields
|
44
|
+
{ name: :card_number, validation: :card_number_validation, required: true },
|
45
|
+
{ name: :expiration_month, validation: :expiration_month_validation, required: true },
|
46
|
+
{ name: :expiration_year, validation: :expiration_year_validation, required: true },
|
47
|
+
{ name: :name_on_card, validation: :no_validation, required: true },
|
48
|
+
{ name: :customer_id, validation: :no_validation, required: true },
|
49
|
+
|
50
|
+
# Optional fields
|
51
|
+
{ name: :zip, validation: :zip_validation, required: false }
|
52
|
+
]
|
53
|
+
|
54
|
+
def initialize(attributes={})
|
55
|
+
super(attributes)
|
56
|
+
|
57
|
+
ALL_VALIDATIONS.each do |attribute|
|
58
|
+
presence_validation(attribute) if attribute[:required]
|
59
|
+
send(attribute[:validation], attribute) if value_for(attribute).present?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Gateway
|
3
|
+
class Customer < PayCertify::Gateway::Base::Resource
|
4
|
+
|
5
|
+
API_ENDPOINT = '/ws/recurring.asmx/ManageCustomer'
|
6
|
+
|
7
|
+
ATTRIBUTES = [
|
8
|
+
:app_customer_id, :name, :customer_id, :type, :address, :city,
|
9
|
+
:state, :zip, :phone, :fax, :email, :status
|
10
|
+
]
|
11
|
+
|
12
|
+
attr_accessor *ATTRIBUTES
|
13
|
+
|
14
|
+
def save!
|
15
|
+
super
|
16
|
+
self.customer_id = self.response['response']['customer_key']
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def attributes_to_gateway_format
|
21
|
+
formatted = super
|
22
|
+
attribute_mapping = PayCertify::Gateway::AttributeMapping
|
23
|
+
formatted.merge! attribute_mapping.type(self)
|
24
|
+
formatted
|
25
|
+
end
|
26
|
+
|
27
|
+
class Validation < PayCertify::Gateway::Base::Validation
|
28
|
+
ALL_VALIDATIONS = [
|
29
|
+
# Mandatory fields
|
30
|
+
{ name: :app_customer_id, validation: :no_validation, required: true },
|
31
|
+
{ name: :type, validation: :type_validation, required: true },
|
32
|
+
{ name: :name, validation: :no_validation, required: true },
|
33
|
+
|
34
|
+
# Optional fields
|
35
|
+
{ name: :zip, validation: :zip_validation, required: false },
|
36
|
+
{ name: :email, validation: :email_validation, required: false },
|
37
|
+
{ name: :status, validation: :status_validation, required: false }
|
38
|
+
]
|
39
|
+
|
40
|
+
ALLOWED_TYPES = %w(add update delete)
|
41
|
+
ALLOWED_STATUSES = %w(active inactive pending closed)
|
42
|
+
|
43
|
+
def initialize(attributes={})
|
44
|
+
super(attributes)
|
45
|
+
|
46
|
+
ALL_VALIDATIONS.each do |attribute|
|
47
|
+
presence_validation(attribute) if attribute[:required]
|
48
|
+
send(attribute[:validation], attribute) if value_for(attribute).present?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def type_validation(attribute)
|
53
|
+
unless value_for(attribute).try(:to_s).in?(ALLOWED_TYPES)
|
54
|
+
add_error(attribute, "Must be one of #{ALLOWED_TYPES.join(', ')}")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -8,24 +8,27 @@ module PayCertify
|
|
8
8
|
|
9
9
|
def initialize(response)
|
10
10
|
self.status = response.status
|
11
|
-
self.original_body = Hash.from_xml(response.body)
|
11
|
+
self.original_body = Hash.from_xml(response.body)
|
12
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
|
-
)
|
13
|
+
super(convert_hash_keys(original_body))
|
24
14
|
end
|
25
15
|
|
26
|
-
|
27
|
-
|
28
|
-
|
16
|
+
private
|
17
|
+
def underscore_key(k)
|
18
|
+
k.to_s.underscore.to_sym
|
19
|
+
end
|
20
|
+
|
21
|
+
def convert_hash_keys(value)
|
22
|
+
case value
|
23
|
+
when Array
|
24
|
+
value.map { |v| convert_hash_keys(v) }
|
25
|
+
# or `value.map(&method(:convert_hash_keys))`
|
26
|
+
when Hash
|
27
|
+
Hash[value.map { |k, v| [underscore_key(k), convert_hash_keys(v)] }]
|
28
|
+
else
|
29
|
+
value
|
30
|
+
end
|
31
|
+
end
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
@@ -1,54 +1,116 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
1
3
|
module PayCertify
|
2
4
|
class Gateway
|
3
|
-
class Transaction
|
4
|
-
|
5
|
-
TRANSACTION_PATH = '/ws/encgateway2.asmx/ProcessCreditCard'
|
6
|
-
|
7
|
-
attr_accessor :client, :original_attributes
|
5
|
+
class Transaction < PayCertify::Gateway::Base::Resource
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
:
|
7
|
+
API_ENDPOINT = '/ws/encgateway2.asmx/ProcessCreditCard'
|
8
|
+
|
9
|
+
ATTRIBUTES = [
|
10
|
+
:transaction_id, :type, :amount, :currency, :card_number, :expiration_month, :expiration_year,
|
11
|
+
:name_on_card, :cvv, :billing_address, :billing_city, :billing_state, :billing_country,
|
12
|
+
:billing_zip, :shipping_address, :shipping_city, :shipping_state, :shipping_country, :shipping_zip,
|
13
|
+
:email, :phone, :ip, :order_description, :customer_id
|
14
|
+
]
|
13
15
|
|
14
|
-
|
15
|
-
self.original_attributes = attributes
|
16
|
-
self.client = client
|
16
|
+
attr_accessor *ATTRIBUTES
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
def save!
|
19
|
+
super
|
20
|
+
self.transaction_id = response['response']['pn_ref']
|
21
|
+
self
|
21
22
|
end
|
22
23
|
|
23
|
-
def
|
24
|
-
|
24
|
+
def success?
|
25
|
+
super && response['response']['result'] == '0'
|
25
26
|
end
|
26
27
|
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
)
|
28
|
+
def attributes_to_gateway_format
|
29
|
+
formatted = super
|
30
|
+
attribute_mapping = PayCertify::Gateway::AttributeMapping
|
31
|
+
formatted.merge! attribute_mapping.expiration_date(self)
|
32
|
+
formatted.merge! attribute_mapping.type(self)
|
33
|
+
formatted
|
32
34
|
end
|
33
35
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
36
|
+
class Validation < PayCertify::Gateway::Base::Validation
|
37
|
+
ALL_VALIDATIONS = [
|
38
|
+
# Mandatory fields
|
39
|
+
{ name: :type, validation: :type_validation, required: true },
|
40
|
+
{ name: :amount, validation: :amount_validation, required: true },
|
41
|
+
{ name: :currency, validation: :currency_validation, required: true },
|
42
|
+
{ name: :card_number, validation: :card_number_validation, required: true },
|
43
|
+
{ name: :expiration_month, validation: :expiration_month_validation, required: true },
|
44
|
+
{ name: :expiration_year, validation: :expiration_year_validation, required: true },
|
45
|
+
{ name: :name_on_card, validation: :no_validation, required: true },
|
46
|
+
{ name: :cvv, validation: :no_validation, required: true },
|
47
|
+
{ name: :transaction_id, validation: :no_validation, required: true },
|
48
|
+
{ name: :billing_city, validation: :no_validation, required: true },
|
49
|
+
{ name: :billing_state, validation: :no_validation, required: true },
|
50
|
+
{ name: :billing_country, validation: :no_validation, required: true },
|
51
|
+
{ name: :billing_zip, validation: :zip_validation, required: true },
|
52
|
+
|
53
|
+
# Optional fields
|
54
|
+
{ name: :shipping_zip, validation: :zip_validation, required: false },
|
55
|
+
{ name: :email, validation: :email_validation, required: false },
|
56
|
+
{ name: :ip, validation: :ip_validation, required: false }
|
57
|
+
]
|
58
|
+
|
59
|
+
CAPTURE_VALIDATIONS = [
|
60
|
+
{ name: :type, validation: :type_validation, required: true },
|
61
|
+
{ name: :amount, validation: :amount_validation, required: true },
|
62
|
+
{ name: :transaction_id, validation: :no_validation, required: true }
|
63
|
+
]
|
38
64
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
end
|
65
|
+
VOID_VALIDATIONS = [
|
66
|
+
{ name: :type, validation: :type_validation, required: true },
|
67
|
+
# { name: :amount, validation: :amount_validation, required: true },
|
68
|
+
{ name: :transaction_id, validation: :no_validation, required: true }
|
69
|
+
]
|
45
70
|
|
46
|
-
|
47
|
-
|
71
|
+
ALLOWED_TYPES = %w(sale auth return void force recurring)
|
72
|
+
ALLOWED_CURRENCIES = %w(USD EUR)
|
48
73
|
|
49
|
-
|
74
|
+
def initialize(attributes={})
|
75
|
+
super(attributes)
|
76
|
+
|
77
|
+
validations.each do |attribute|
|
78
|
+
presence_validation(attribute) if attribute[:required]
|
79
|
+
send(attribute[:validation], attribute) if value_for(attribute).present?
|
50
80
|
end
|
51
81
|
end
|
82
|
+
|
83
|
+
def validations
|
84
|
+
case attributes[:type]
|
85
|
+
when 'force'
|
86
|
+
CAPTURE_VALIDATIONS
|
87
|
+
when 'void', 'return'
|
88
|
+
VOID_VALIDATIONS
|
89
|
+
else
|
90
|
+
ALL_VALIDATIONS
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def type_validation(attribute)
|
95
|
+
unless value_for(attribute).try(:to_s).in?(ALLOWED_TYPES)
|
96
|
+
add_error(attribute, "Must be one of #{ALLOWED_TYPES.join(', ')}")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def currency_validation(attribute)
|
101
|
+
set_attribute(attribute, value_for(attribute).upcase)
|
102
|
+
|
103
|
+
unless value_for(attribute).try(:to_s).in?(ALLOWED_CURRENCIES)
|
104
|
+
add_error(attribute, "Must be one of #{ALLOWED_CURRENCIES.join(', ')}")
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def ip_validation(attribute)
|
109
|
+
IPAddr.new(value_for(attribute))
|
110
|
+
rescue IPAddr::InvalidAddressError
|
111
|
+
add_error(attribute, "Doesn't validate as an IP.")
|
112
|
+
end
|
113
|
+
end
|
52
114
|
end
|
53
115
|
end
|
54
116
|
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module PayCertify
|
2
|
+
class Insurance
|
3
|
+
|
4
|
+
class NoCredentialsError < StandardError; end
|
5
|
+
|
6
|
+
API_ENDPOINT = 'https://connect.paycertify.com/'
|
7
|
+
|
8
|
+
MANDATORY_FIELDS = [
|
9
|
+
:firstname, :lastname, :email, :order_number, :items_ordered, :charge_amount,
|
10
|
+
:billing_address, :billing_address2, :billing_city, :billing_state, :billing_country, :billing_zip_code
|
11
|
+
]
|
12
|
+
|
13
|
+
OPTIONAL_FIELDS = [
|
14
|
+
:phone, :shipping_address, :shipping_address2, :shipping_city,
|
15
|
+
:shipping_state, :shipping_country, :shipping_zip_code, :shipping_carrier, :tracking_number
|
16
|
+
]
|
17
|
+
|
18
|
+
attr_accessor :attributes, :errors, :response
|
19
|
+
|
20
|
+
delegate :client_id, :token, to: :class
|
21
|
+
|
22
|
+
def initialize(attributes)
|
23
|
+
raise NoCredentialsError, 'No token found for api_client/secret/client_id combination.' unless token.present?
|
24
|
+
|
25
|
+
self.attributes = HashWithIndifferentAccess.new(attributes)
|
26
|
+
self.errors = {}
|
27
|
+
|
28
|
+
validate!
|
29
|
+
end
|
30
|
+
|
31
|
+
def success?
|
32
|
+
response.success?
|
33
|
+
end
|
34
|
+
|
35
|
+
def save!
|
36
|
+
data = attributes.slice *[MANDATORY_FIELDS + OPTIONAL_FIELDS].flatten
|
37
|
+
data[:ship_to_billing_addr] = true unless data[:shipping_address].present?
|
38
|
+
|
39
|
+
api_response = connection.post do |request|
|
40
|
+
request.url path_for('orders')
|
41
|
+
request.headers['Content-Type'] = 'application/json'
|
42
|
+
request.headers['Authorization'] = "JWT #{token}"
|
43
|
+
request.body = JSON.generate(data)
|
44
|
+
end
|
45
|
+
|
46
|
+
self.response = Response.new(api_response)
|
47
|
+
self.errors = errors.merge(response) unless response.success?
|
48
|
+
|
49
|
+
response
|
50
|
+
end
|
51
|
+
|
52
|
+
class << self
|
53
|
+
attr_accessor :api_public_key, :api_secret_key, :client_id, :token
|
54
|
+
|
55
|
+
def configure(&block)
|
56
|
+
yield self if block_given?
|
57
|
+
|
58
|
+
connection = Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
|
59
|
+
faraday.request :url_encoded
|
60
|
+
faraday.response :logger
|
61
|
+
faraday.adapter Faraday.default_adapter
|
62
|
+
end
|
63
|
+
|
64
|
+
response = connection.get do |request|
|
65
|
+
request.url 'api/v1/token'
|
66
|
+
request.headers['api-public-key'] = api_public_key
|
67
|
+
request.headers['api-secret-key'] = api_secret_key
|
68
|
+
request.headers['api-client-id'] = client_id
|
69
|
+
end
|
70
|
+
|
71
|
+
json = JSON.parse(response.body)
|
72
|
+
|
73
|
+
self.token = json['jwt']
|
74
|
+
|
75
|
+
return {
|
76
|
+
api_public_key: api_public_key,
|
77
|
+
api_secret_key: api_secret_key,
|
78
|
+
client_id: client_id,
|
79
|
+
token: token
|
80
|
+
}
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Response < HashWithIndifferentAccess
|
85
|
+
attr_accessor :original_response
|
86
|
+
|
87
|
+
def initialize(response)
|
88
|
+
self.original_response = response
|
89
|
+
super JSON.parse(response.body)
|
90
|
+
end
|
91
|
+
|
92
|
+
def success?
|
93
|
+
original_response.status < 400
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
def validate!
|
99
|
+
MANDATORY_FIELDS.each do |field|
|
100
|
+
unless attributes[field].present?
|
101
|
+
self.errors[field] = "Required attribute not present"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def connection
|
107
|
+
@connection ||= Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
|
108
|
+
faraday.request :url_encoded
|
109
|
+
faraday.response :logger
|
110
|
+
faraday.adapter Faraday.default_adapter
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def path_for(path)
|
115
|
+
return "api/v1/#{client_id}/#{path}/"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/lib/paycertify/three_ds.rb
CHANGED
@@ -19,6 +19,14 @@ module PayCertify
|
|
19
19
|
self['_frictionless_3ds_callback'].present?
|
20
20
|
end
|
21
21
|
|
22
|
+
def handshake
|
23
|
+
self.slice('cavv', 'eci', 'cavv_algorithm', 'xid')
|
24
|
+
end
|
25
|
+
|
26
|
+
def redirect_to(location)
|
27
|
+
"<script>window.location.href = '#{location}'</script>"
|
28
|
+
end
|
29
|
+
|
22
30
|
def authenticate!
|
23
31
|
PayCertify::ThreeDS.authenticate!(settings: session, callback_params: params)
|
24
32
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'faraday'
|
2
|
+
require 'faraday_curl'
|
2
3
|
|
3
4
|
module PayCertify
|
4
5
|
class ThreeDS
|
@@ -56,12 +57,16 @@ module PayCertify
|
|
56
57
|
def connection
|
57
58
|
@connection ||= Faraday.new(url: API_ENDPOINT, ssl: {verify: false}) do |faraday|
|
58
59
|
faraday.request :url_encoded
|
60
|
+
faraday.request :curl, Logger.new(STDOUT), :warn
|
59
61
|
faraday.response :logger
|
60
62
|
faraday.adapter Faraday.default_adapter
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
66
|
def signature(path, data)
|
67
|
+
10.times{puts}
|
68
|
+
puts "#{api_key}#{path_for(path)}#{data}#{api_secret}"
|
69
|
+
10.times{puts}
|
65
70
|
Digest::SHA256.hexdigest "#{api_key}#{path_for(path)}#{data}#{api_secret}"
|
66
71
|
end
|
67
72
|
|
@@ -46,7 +46,7 @@ module PayCertify
|
|
46
46
|
</script>
|
47
47
|
HTML
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def frictionless
|
51
51
|
html = <<-HTML.squish
|
52
52
|
<style> #frame { display: none; } </style>
|
@@ -69,42 +69,40 @@ module PayCertify
|
|
69
69
|
var frame = document.getElementById('frame');
|
70
70
|
var form = document.getElementById('callback-form');
|
71
71
|
var interval = 500;
|
72
|
-
var timeout = interval *
|
73
|
-
var counter = 0;
|
72
|
+
var timeout = interval * 15;
|
74
73
|
|
75
74
|
frame.contentDocument.write('#{form}');
|
76
75
|
frame.contentDocument.form3ds.submit();
|
77
76
|
|
78
|
-
setInterval(function() {
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
try {
|
83
|
-
var frameContent = frame.contentDocument;
|
84
|
-
var frameDoc = frameContent.documentElement;
|
77
|
+
var interval = setInterval(function() {
|
78
|
+
try {
|
79
|
+
var frameContent = frame.contentDocument;
|
80
|
+
var frameDoc = frameContent.documentElement;
|
85
81
|
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
var input;
|
82
|
+
var text = frameContent.body.innerHTML || frameDoc.textContent || frameDoc.innerText;
|
83
|
+
var json = JSON.parse(text);
|
90
84
|
|
91
|
-
|
92
|
-
input = document.createElement('input');
|
93
|
-
input.type = 'hidden';
|
94
|
-
input.name = key;
|
95
|
-
input.value = json[key];
|
85
|
+
var input;
|
96
86
|
|
97
|
-
|
98
|
-
|
87
|
+
for(key in json) {
|
88
|
+
input = document.createElement('input');
|
89
|
+
input.type = 'hidden';
|
90
|
+
input.name = key;
|
91
|
+
input.value = json[key];
|
99
92
|
|
100
|
-
form.
|
101
|
-
} catch(e) {
|
102
|
-
return false;
|
93
|
+
form.appendChild(input);
|
103
94
|
};
|
104
|
-
|
95
|
+
|
96
|
+
clearInterval(interval);
|
105
97
|
form.submit();
|
106
|
-
}
|
98
|
+
} catch(e) {
|
99
|
+
return false;
|
100
|
+
};
|
107
101
|
}, interval);
|
102
|
+
|
103
|
+
setTimeout(function() {
|
104
|
+
form.submit();
|
105
|
+
}, timeout);
|
108
106
|
})();
|
109
107
|
</script>
|
110
108
|
HTML
|
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.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- PayCertify Engineering Team
|
@@ -38,6 +38,20 @@ dependencies:
|
|
38
38
|
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: faraday_curl
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
41
55
|
description: Interact with the Gateway, 3DS, Kount, and FraudPortal
|
42
56
|
email: engineering@paycertify.com
|
43
57
|
executables: []
|
@@ -45,12 +59,19 @@ extensions: []
|
|
45
59
|
extra_rdoc_files: []
|
46
60
|
files:
|
47
61
|
- lib/paycertify.rb
|
62
|
+
- lib/paycertify/confirmation.rb
|
48
63
|
- lib/paycertify/gateway.rb
|
49
64
|
- lib/paycertify/gateway/attribute_mapping.rb
|
65
|
+
- lib/paycertify/gateway/base.rb
|
66
|
+
- lib/paycertify/gateway/base/resource.rb
|
67
|
+
- lib/paycertify/gateway/base/validation.rb
|
68
|
+
- lib/paycertify/gateway/charge.rb
|
50
69
|
- lib/paycertify/gateway/client.rb
|
70
|
+
- lib/paycertify/gateway/credit_card.rb
|
71
|
+
- lib/paycertify/gateway/customer.rb
|
51
72
|
- lib/paycertify/gateway/response.rb
|
52
73
|
- lib/paycertify/gateway/transaction.rb
|
53
|
-
- lib/paycertify/
|
74
|
+
- lib/paycertify/insurance.rb
|
54
75
|
- lib/paycertify/three_ds.rb
|
55
76
|
- lib/paycertify/three_ds/callback.rb
|
56
77
|
- lib/paycertify/three_ds/client.rb
|
@@ -1,174 +0,0 @@
|
|
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
|