yp-ruby 0.1.3 → 0.1.4a

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c0e8f0cb22b7a4f03a355e460984e1ac50da242b
4
- data.tar.gz: fa63594396cabc343e2699290b1223c762c903fb
3
+ metadata.gz: 9f28691645bfdb90373e2524028d93a1e711bb46
4
+ data.tar.gz: cd6b81278539fbea582bf11dca079e7131f45357
5
5
  SHA512:
6
- metadata.gz: 9e96e8bfbb983d3336d1b81798087b6d81c64b1631f748488fccaa8206210aa325d1b4c5341523192a141bdb5c80d61882247fad4767a8d1e548a2145c487eff
7
- data.tar.gz: 01ec3ed018db1179656697a4ea112b493e04021d3453ff58519f45041abc6d9623b6bd348fe267bb4f1206ee501c32590912cc84fae86fd7af495694740a5ac2
6
+ metadata.gz: 4f933e44afc37aa9a3a244e89c78f6a7ef4f75e6ca20cb8e95dbf57df2557b6fa73afe5ea6b37aa5d0cfefd84ab625bdec24f105c09d16e8e7d712873d766a97
7
+ data.tar.gz: 299c5520a8dd0234828004f7e5d16e1b4ad404f977deddf700010606b02fb4423e475c933c201964e9c9d36852bfe57b631e433565032dc27a80a4250468f4da
data/README.md CHANGED
@@ -20,7 +20,86 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
- TODO: Write usage instructions here
23
+ Different classes are available for the Gateway actions:
24
+
25
+ - Yp::Sale
26
+ - Yp::Preauth
27
+ - Yp::Verify
28
+
29
+ If you need support for more actions, please [open an issue](https://github.com/andytango/yp-ruby/issues)!
30
+
31
+ ### Authentication and params
32
+
33
+ You will need to pass in the correct parameters according to the Yorkshire Payments integration guide:
34
+
35
+ ```ruby
36
+
37
+ signature_key = 'Engine0Milk12Next'
38
+
39
+ params = {
40
+ merchantID: '101381',
41
+ countryCode: 826,
42
+ currencyCode: 826,
43
+ cardNumber: '4012001037141112',
44
+ cardExpiryMonth: 12,
45
+ cardExpiryYear: 15,
46
+ cardCVV: '083',
47
+ customerName: 'Yorkshire Payments',
48
+ customerEmail: 'support@yorkshirepayments.com',
49
+ customerAddress: '16 Test Street',
50
+ customerPostCode: 'TE15 5ST',
51
+ orderRef: 'Test purchase'
52
+ }
53
+
54
+ transaction = Yp::Sale.new(signature, params)
55
+
56
+ ```
57
+
58
+ The library will *not* validate your parameters before sending. This feature is coming soon.
59
+
60
+ You pass in a callback method as a block, or access the response after the transaction is sent:
61
+
62
+ ```ruby
63
+
64
+ transaction.send! do |response|
65
+ puts response.state # --> 'captured'
66
+ end
67
+
68
+ # OR:
69
+
70
+ response = transaction.send!
71
+ puts response.state # --> 'captured'
72
+
73
+ ```
74
+
75
+ ### Error Handling
76
+
77
+ The client will raise a number of different types of errors:
78
+
79
+ #### Validation Errors
80
+
81
+ These occur if we could not perform any validation. This means the response is
82
+ malformed and can arise if you did not correctly sign your request:
83
+
84
+ - Yp::Response::InvalidSignatureError (potentially malicious)
85
+ - Yp::Response::MissingSignatureError
86
+ - Yp::Response::MissingResponseCodeError
87
+
88
+ #### Gateway Errors
89
+
90
+ These occur if, during validation, we found a Gateway error code. These come in
91
+ three different flavours:
92
+
93
+ - Yp::Response::MissingFieldError: _a field was missing in your request_
94
+ - Yp::Response::InvalidFieldError: _a field value was invalid in your request_
95
+ - Yp::Response::GatewayError: _the error could not be classified. The gateway's
96
+ error message will be supplied_
97
+
98
+ #### Transaction Declined Error
99
+
100
+ This occurs if the transaction has entered the **Declined** state:
101
+
102
+ - Yp::Response::DeclinedError
24
103
 
25
104
  ## Development
26
105
 
@@ -30,7 +109,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
30
109
 
31
110
  ## Contributing
32
111
 
33
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/yp-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
112
+ Bug reports and pull requests are welcome on GitHub at https://github.com/andytango/yp-ruby. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
34
113
 
35
114
 
36
115
  ## License
@@ -0,0 +1,69 @@
1
+ missing_field:
2
+ 66048: 'request'
3
+ 66049: 'merchantID'
4
+ 66055: 'action'
5
+ 66056: 'amount'
6
+ 66057: 'currencyCode'
7
+ 66058: 'cardNumber'
8
+ 66059: 'cardExpiryMonth'
9
+ 66060: 'cardExpiryYear'
10
+ 66064: 'cardCVV'
11
+ 66065: 'customerName'
12
+ 66066: 'customerAddress'
13
+ 66067: 'customerPostCode'
14
+ 66068: 'customerEmail'
15
+ 66070: 'countyCode'
16
+ 66087: 'signature'
17
+ 66092: 'threeDSPaRes'
18
+ 66093: 'threeDSECI'
19
+ 66094: 'threeDSCAVV'
20
+ 66095: 'threeDSXID'
21
+ 66096: 'threeDSEnrolled'
22
+ 66097: 'threeDSAuthenticated'
23
+ 66098: 'threeDSCheckPref'
24
+ 66099: 'cv2CheckPref'
25
+ 66100: 'addressCheckPref'
26
+ 66101: 'postcodeCheckPref'
27
+ 66102: 'captureDelay'
28
+ 66103: 'orderDate'
29
+ 66104: 'grossAmount'
30
+ 66105: 'netAmount'
31
+ 66016: 'taxRate'
32
+ 66016: 'taxReason'
33
+ 66160: 'cardExpiryDate'
34
+ 66161: 'cardStartDate'
35
+
36
+ invalid_field:
37
+ 66304: 'request'
38
+ 66305: 'merchantID'
39
+ 66311: 'action'
40
+ 66312: 'amount'
41
+ 66313: 'currencyCode'
42
+ 66314: 'cardNumber'
43
+ 66315: 'cardExpiryMonth'
44
+ 66316: 'cardExpiryYear'
45
+ 66317: 'cardCVV'
46
+ 66332: 'customerName'
47
+ 66322: 'customerAddress'
48
+ 66323: 'customerPostCode'
49
+ 66324: 'customerEmail'
50
+ 66326: 'countyCode'
51
+ 66343: 'signature'
52
+ 66348: 'threeDSPaRes'
53
+ 66349: 'threeDSECI'
54
+ 66350: 'threeDSCAVV'
55
+ 66351: 'threeDSXID'
56
+ 66352: 'threeDSEnrolled'
57
+ 66353: 'threeDSAuthenticated'
58
+ 66354: 'threeDSCheckPref'
59
+ 66355: 'cv2CheckPref'
60
+ 66356: 'addressCheckPref'
61
+ 66357: 'postcodeCheckPref'
62
+ 66358: 'captureDelay'
63
+ 66359: 'orderDate'
64
+ 66360: 'grossAmount'
65
+ 66361: 'netAmount'
66
+ 66362: 'taxRate'
67
+ 66363: 'taxReason'
68
+ 66416: 'cardExpiryDate'
69
+ 66417: 'cardStartDate'
data/lib/base.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  require 'transaction_logger'
2
+ require 'signing_hash_creator'
3
+ require 'response'
2
4
 
3
5
  module Yp
4
6
  class Base
@@ -15,24 +17,13 @@ module Yp
15
17
  @signature_key = signature_key
16
18
  end
17
19
 
18
- def body
19
- @params.clone.tap do |params|
20
- params[:signature] = create_signing_hash
21
- TransactionLogger.log_request(params)
22
- end
23
- end
24
-
25
- def create_signing_hash
26
- self.class.digest(self.class.serialize_params(@params) + @signature_key)
27
- end
28
-
29
- def send
20
+ def send!
30
21
  if block_given?
31
22
  RestClient.post(URL, body) do |response|
32
- yield(self.class.parse response)
23
+ yield(parse_and_validate response)
33
24
  end
34
25
  else
35
- self.class.parse(RestClient.post(URL, body))
26
+ parse_and_validate(RestClient.post(URL, body))
36
27
  end
37
28
  end
38
29
 
@@ -59,42 +50,22 @@ module Yp
59
50
  { type: TYPE[type] }
60
51
  end
61
52
 
62
- class << self
63
-
64
- def digest(str_params)
65
- Digest::SHA512.hexdigest str_params
66
- end
67
-
68
- def serialize_params(params)
69
- uri_string_from_hash(params)
70
- end
71
-
72
- def parse(response)
73
- ruby_hash_from_response(CGI::parse(response)).tap do |parsed|
74
- TransactionLogger.log_response(parsed)
75
- end
76
- end
77
-
78
- private
79
-
80
- def uri_string_from_hash(hash)
81
- hash.map { |key, value| uri_query_param(key, value) }.sort.join '&'
82
- end
83
-
84
- def uri_query_param(key, value)
85
- encode_url_component(key) + '=' + encode_url_component(value)
86
- end
87
-
88
- def encode_url_component(key)
89
- ERB::Util::url_encode(key).gsub(/%20/, '+')
53
+ def body
54
+ @params.clone.tap do |params|
55
+ params[:signature] = create_signing_hash
56
+ TransactionLogger.log_request(params)
90
57
  end
58
+ end
91
59
 
92
- def ruby_hash_from_response(hash)
93
- hash.reduce({}) do |memo, (key, value)|
94
- memo.merge({key.to_sym => value.first})
95
- end
96
- end
60
+ def create_signing_hash
61
+ SigningHashCreator.new(@params, @signature_key).create
62
+ end
97
63
 
64
+ def parse_and_validate(response)
65
+ Response
66
+ .new(@signature_key, response, TransactionLogger)
67
+ .parse_and_validate
98
68
  end
69
+
99
70
  end
100
71
  end
data/lib/response.rb ADDED
@@ -0,0 +1,36 @@
1
+ require 'response/parser'
2
+ require 'response/error'
3
+ require 'response/validator'
4
+ require 'response/error_logger'
5
+
6
+ module Yp
7
+ class Response
8
+ def initialize(signature, params, logger)
9
+ @signature = signature
10
+ @params = params
11
+ @logger = logger
12
+ end
13
+
14
+ def parse_and_validate
15
+ validate!
16
+ parsed
17
+ end
18
+
19
+ private
20
+
21
+ def validate!
22
+ ErrorLogger.new(@logger).log do
23
+ Validator.new(parsed, @signature).validate!
24
+ end
25
+ end
26
+
27
+ def parsed
28
+ @parsed ||= parse_params
29
+ end
30
+
31
+ def parse_params
32
+ Parser.new(@params).parse.tap { |parsed| @logger.log_response(parsed) }
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,16 @@
1
+ module Yp
2
+ class Response
3
+ class ValidationError < StandardError
4
+ def initialize
5
+ super('Gateway response could not be validated.')
6
+ end
7
+ end
8
+
9
+ class InvalidSignatureError < ValidationError; end
10
+ class MissingSignatureError < ValidationError; end
11
+ class MissingResponseCodeError < ValidationError; end
12
+ class MissingResponseMessageError < ValidationError; end
13
+
14
+ class DeclinedError < StandardError; end
15
+ end
16
+ end
@@ -0,0 +1,53 @@
1
+ module Yp
2
+ class Response
3
+ class ErrorLogger
4
+ extend Forwardable
5
+
6
+ def_delegators :@logger, :fatal, :error
7
+
8
+ def initialize(logger)
9
+ @logger = logger
10
+ end
11
+
12
+ def log
13
+ begin
14
+ yield
15
+ rescue InvalidSignatureError => e
16
+ log_fatal 'An invalid signature was received', e
17
+ rescue MissingSignatureError => e
18
+ log_not_found 'Signature', e
19
+ rescue MissingResponseCodeError => e
20
+ log_not_found 'Response Code', e
21
+ rescue MissingResponseMessageError => e
22
+ log_not_found 'Response Message', e
23
+ rescue DeclinedError => e
24
+ log_error 'Transaction was declined by the acquirer', e
25
+ rescue MissingFieldError => e
26
+ log_error 'Gateway responded with missing field', e
27
+ rescue InvalidFieldError => e
28
+ log_error 'Gateway responded with invalid field', e
29
+ rescue GatewayError => e
30
+ log_error 'Gateway responded with error', e
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def log_not_found(thing, e)
37
+ log_fatal("#{thing} was not found in the response", e)
38
+ end
39
+
40
+ def log_fatal(str, e)
41
+ fatal(str)
42
+ raise e
43
+ end
44
+
45
+ def log_error(str, e)
46
+ error("#{str} (#{e})")
47
+ raise e
48
+ end
49
+
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,68 @@
1
+ require 'yaml'
2
+
3
+ module Yp
4
+ class Response
5
+ class GatewayError < StandardError
6
+
7
+ def self.from_response_code(code, message)
8
+ Factory.new(code, message).error
9
+ end
10
+
11
+ class Factory
12
+
13
+ class << self
14
+ def error_map
15
+ @error_map ||= load_error_map
16
+ end
17
+
18
+ private
19
+
20
+ def load_error_map
21
+ YAML.load(File.read('data/gateway_responses.yml'))
22
+ end
23
+ end
24
+
25
+ def initialize(code, message)
26
+ @code = code
27
+ @message = message
28
+ end
29
+
30
+ def error
31
+ if is_missing_field?
32
+ MissingFieldError.new(missing_field)
33
+ elsif is_invalid_field?
34
+ InvalidFieldError.new(invalid_field)
35
+ else
36
+ GatewayError.new(@message)
37
+ end
38
+ end
39
+
40
+ private
41
+
42
+ def is_missing_field?
43
+ !missing_field.nil?
44
+ end
45
+
46
+ def missing_field
47
+ @missing_field ||= mapped_error(:missing_field)
48
+ end
49
+
50
+ def is_invalid_field?
51
+ !invalid_field.nil?
52
+ end
53
+
54
+ def invalid_field
55
+ @invalid_field ||= mapped_error(:invalid_field)
56
+ end
57
+
58
+ def mapped_error(sym)
59
+ Factory.error_map[sym.to_s][@code.to_i]
60
+ end
61
+
62
+ end
63
+ end
64
+
65
+ class MissingFieldError < GatewayError; end
66
+ class InvalidFieldError < GatewayError; end
67
+ end
68
+ end
@@ -0,0 +1,27 @@
1
+ module Yp
2
+ class Response
3
+ class Parser
4
+
5
+ def initialize(params)
6
+ @params = params
7
+ end
8
+
9
+ def parse
10
+ ruby_hash_from_response(parse_string)
11
+ end
12
+
13
+ private
14
+
15
+ def parse_string
16
+ CGI::parse(@params)
17
+ end
18
+
19
+ def ruby_hash_from_response(hash)
20
+ hash.reduce({}) do |memo, (key, value)|
21
+ memo.merge({key.to_sym => value.first})
22
+ end
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ require_relative 'validator/signing_key'
2
+ require_relative 'validator/gateway'
3
+ require_relative 'validator/acquirer'
4
+
5
+ module Yp
6
+ class Response
7
+ class Validator
8
+ def initialize(params, signature)
9
+ @params = params
10
+ @signature = signature
11
+ end
12
+
13
+ def validate!
14
+ SigningKey.new(@params, @signature).validate!
15
+ Gateway.new(@params).validate!
16
+ Acquirer.new(@params).validate!
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Yp
2
+ class Response
3
+ class Validator
4
+ class Acquirer
5
+
6
+ def initialize(params)
7
+ @params = params
8
+ end
9
+
10
+ def validate!
11
+ !declined? || (raise DeclinedError)
12
+ end
13
+
14
+ private
15
+
16
+ def declined?
17
+ response_code == 4 || response_code == 5
18
+ end
19
+
20
+ def response_code
21
+ @response_code ||= @params[:responseCode].to_i
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ require_relative '../gateway_error'
2
+
3
+ module Yp
4
+ class Response
5
+ class Validator
6
+ class Gateway
7
+
8
+ def initialize(params)
9
+ @params = params
10
+ end
11
+
12
+ def validate!
13
+ is_valid_response_code? || (raise error_from_response)
14
+ end
15
+
16
+ private
17
+
18
+ def is_valid_response_code?
19
+ response_code.to_i <= 5
20
+ end
21
+
22
+ def error_from_response
23
+ GatewayError.from_response_code(response_code, response_message)
24
+ end
25
+
26
+ def response_code
27
+ @response_code ||= find_response_code
28
+ end
29
+
30
+ def find_response_code
31
+ @params[:responseCode] || (raise MissingResponseCodeError)
32
+ end
33
+
34
+ def response_message
35
+ @params[:responseMessage] || (raise MissingResponseMessageError)
36
+ end
37
+
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,38 @@
1
+ require 'signing_hash_creator'
2
+
3
+ module Yp
4
+ class Response
5
+ class Validator
6
+ class SigningKey
7
+
8
+ def initialize(params, signature)
9
+ @params = params
10
+ @signature = signature
11
+ end
12
+
13
+ def validate!
14
+ has_valid_signing_key? || (raise InvalidSignatureError)
15
+ end
16
+
17
+ private
18
+
19
+ def has_valid_signing_key?
20
+ their_signing_key == our_signing_key
21
+ end
22
+
23
+ def their_signing_key
24
+ @params[:signature] || (raise MissingSignatureError)
25
+ end
26
+
27
+ def our_signing_key
28
+ SigningHashCreator.new(signing_key_params, @signature).create
29
+ end
30
+
31
+ def signing_key_params
32
+ @params.reject { |k, _| k == :signature }
33
+ end
34
+
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,32 @@
1
+ module Yp
2
+ class SigningHashCreator
3
+ def initialize(params, signature_key)
4
+ @params = params
5
+ @signature_key = signature_key
6
+ end
7
+
8
+ def create
9
+ self.class.digest(self.class.serialize_params(@params) + @signature_key)
10
+ end
11
+
12
+ class << self
13
+
14
+ def digest(str_params)
15
+ Digest::SHA512.hexdigest str_params
16
+ end
17
+
18
+ def serialize_params(params)
19
+ params.map { |key, value| uri_query_param(key, value) }.sort.join('&')
20
+ end
21
+
22
+ def uri_query_param(key, value)
23
+ encode_url_component(key) + '=' + encode_url_component(value)
24
+ end
25
+
26
+ def encode_url_component(key)
27
+ ERB::Util::url_encode(key).gsub(/%20/, '+')
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -12,10 +12,22 @@ module Yp
12
12
  info 'Response received', params
13
13
  end
14
14
 
15
+ def info(str, params=nil)
16
+ Yp.logger.info(format(str, params))
17
+ end
18
+
19
+ def error(str)
20
+ Yp.logger.error(format_message(str))
21
+ end
22
+
23
+ def fatal(str)
24
+ Yp.logger.fatal(format_message(str))
25
+ end
26
+
15
27
  private
16
28
 
17
- def info(str, params=nil)
18
- Yp.logger.info("#{format_message(str)} #{format_params(params)}")
29
+ def format(str, params)
30
+ "#{format_message(str)} #{format_params(params)}"
19
31
  end
20
32
 
21
33
  def format_message(str)
@@ -23,7 +35,7 @@ module Yp
23
35
  end
24
36
 
25
37
  def format_params(params)
26
- " with params #{filter_card_params(params).to_s}"
38
+ " with params #{filter_card_params(params).to_s}" unless params.nil?
27
39
  end
28
40
 
29
41
  def filter_card_params(hash)
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Yp
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4a"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yp-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4a
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Hall
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-01-21 00:00:00.000000000 Z
11
+ date: 2017-03-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -133,12 +133,23 @@ files:
133
133
  - Rakefile
134
134
  - bin/console
135
135
  - bin/setup
136
+ - data/gateway_responses.yml
136
137
  - exe/yp-ruby
137
138
  - lib/base.rb
138
139
  - lib/factory.rb
139
140
  - lib/nil_logger.rb
140
141
  - lib/preauth.rb
142
+ - lib/response.rb
143
+ - lib/response/error.rb
144
+ - lib/response/error_logger.rb
145
+ - lib/response/gateway_error.rb
146
+ - lib/response/parser.rb
147
+ - lib/response/validator.rb
148
+ - lib/response/validator/acquirer.rb
149
+ - lib/response/validator/gateway.rb
150
+ - lib/response/validator/signing_key.rb
141
151
  - lib/sale.rb
152
+ - lib/signing_hash_creator.rb
142
153
  - lib/transaction_logger.rb
143
154
  - lib/verify.rb
144
155
  - lib/version.rb
@@ -160,9 +171,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
160
171
  version: '0'
161
172
  required_rubygems_version: !ruby/object:Gem::Requirement
162
173
  requirements:
163
- - - ">="
174
+ - - ">"
164
175
  - !ruby/object:Gem::Version
165
- version: '0'
176
+ version: 1.3.1
166
177
  requirements: []
167
178
  rubyforge_project:
168
179
  rubygems_version: 2.5.2