connect-sdk-ruby 1.8.0 → 1.9.0

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: 980877434fcc0d67e0a3b79881d54a496584dff0
4
- data.tar.gz: 84819aa760a8d93e692c0b2cbce97fa7a20a2f7c
3
+ metadata.gz: b1cc2f2e689f0848718eb4a78877c4f83506c06e
4
+ data.tar.gz: a58d8736d1ec8609b0f19d3a358dcbd898c4b8c0
5
5
  SHA512:
6
- metadata.gz: 7299829037d437656d41cee2a9331a67f35cc736d0ab7bfc7a368e771243f6ce62ebd6ba5f32b6080f40ce53f0c0a28fc976dcedf0f2013f897c17a1688f011f
7
- data.tar.gz: 015e6059dc121b9dc0c34085147c173aa854802eaeef3e47a38e8d6cad9917964cebe3cd19774ef8e3973f554b86940c1040252770bf4794de63e75b4b7a7385
6
+ metadata.gz: b0792a215969663067ac9057eac4801847ac65f216f36357d54fe2066fac1d144a5d566f9d485897d5069464cde0cacdd28194f62ce485a475e8eca8c95c2bd0
7
+ data.tar.gz: 25db60ceb4407cd84cf296aa6a918c4c52a5c67b9f42582006a48ddfb98f7cac4b1cbd1d49cb5b6614d5c90c5b94b22d8cd6082e2225fa88cc2a4338697b7666
data/README.md CHANGED
@@ -32,6 +32,7 @@ As for JRuby, version 9.0.0.0 and higher are supported.
32
32
  In addition, the following package is required:
33
33
 
34
34
  * [httpclient](https://github.com/nahi/httpclient) 2.8 or higher
35
+ * [concurrent-ruby](https://github.com/ruby-concurrency/concurrent-ruby) 1.0 or higher
35
36
 
36
37
  ## Installation
37
38
 
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |spec|
2
2
  spec.name = 'connect-sdk-ruby'
3
- spec.version = '1.8.0'
3
+ spec.version = '1.9.0'
4
4
  spec.authors = ['Ingenico ePayments']
5
5
  spec.email = ['github@epay.ingenico.com']
6
6
  spec.summary = %q{SDK to communicate with the Ingenico ePayments platform using the Ingenico Connect Server API}
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
18
18
  spec.required_ruby_version = '>= 2.0'
19
19
 
20
20
  spec.add_dependency 'httpclient', '~> 2.8'
21
+ spec.add_dependency 'concurrent-ruby', '~>1.0'
21
22
 
22
23
  spec.add_development_dependency 'yard', '~> 0.9'
23
24
  spec.add_development_dependency 'rspec', '~> 3.5'
@@ -25,3 +25,4 @@ require prefix + 'meta_data_provider'
25
25
  require prefix + 'factory'
26
26
 
27
27
  require prefix + 'defaultimpl'
28
+ require prefix + 'webhooks'
@@ -6,14 +6,13 @@ module Ingenico::Connect::SDK
6
6
 
7
7
  # {Ingenico::Connect::SDK::Logging::CommunicatorLogger} that logs the messages to $stdout.
8
8
  class StdoutCommunicatorLogger < CommunicatorLogger
9
-
10
9
  include Singleton
11
10
 
12
11
  def initialize
13
12
  # implement the interface
14
13
  end
15
14
 
16
- # NOTE: this is needed to not break method calls based on old interface
15
+ # NOTE: this alias is needed to not break existing method calls depending on old interface
17
16
  class << self
18
17
  alias_method :INSTANCE, :instance
19
18
  end
@@ -5,7 +5,7 @@ module Ingenico::Connect::SDK
5
5
 
6
6
  # Manages metadata about the server using the SDK
7
7
  class MetaDataProvider
8
- @@SDK_VERSION = '1.8.0'
8
+ @@SDK_VERSION = '1.9.0'
9
9
  @@SERVER_META_INFO_HEADER = 'X-GCS-ServerMetaInfo'
10
10
  @@PROHIBITED_HEADERS = [@@SERVER_META_INFO_HEADER, 'X-GCS-Idempotence-Key',
11
11
  'Date', 'Content-Type', 'Authorization'].sort!.freeze
@@ -142,3 +142,7 @@ end
142
142
  # Contains data classes related to shopping cart functionality.
143
143
  module Ingenico::Connect::SDK::Domain::MetaData
144
144
  end
145
+
146
+ # Contains data classes related to webhooks functionality.
147
+ module Ingenico::Connect::SDK::Webhooks
148
+ end
@@ -0,0 +1,11 @@
1
+ prefix = 'ingenico/connect/sdk/webhooks/'
2
+
3
+ require prefix + 'api_version_mismatch_exception'
4
+ require prefix + 'signature_validation_exception'
5
+ require prefix + 'secret_key_not_available_exception'
6
+ require prefix + 'secret_key_store'
7
+ require prefix + 'in_memory_secret_key_store'
8
+ require prefix + 'webhooks_event'
9
+ require prefix + 'webhooks_helper'
10
+ require prefix + 'webhooks_helper_builder'
11
+ require prefix + 'webhooks'
@@ -0,0 +1,20 @@
1
+ module Ingenico::Connect::SDK
2
+ module Webhooks
3
+ # Raised when a webhooks event has an API version that is not supported by current version
4
+ # of SDK.
5
+ class ApiVersionMismatchException < RuntimeError
6
+
7
+ def initialize(event_api_version, sdk_api_version)
8
+ super("event API version #{event_api_version} is not compatible with SDK API version #{sdk_api_version}")
9
+ @event_api_version = event_api_version
10
+ @sdk_api_version = sdk_api_version
11
+ end
12
+
13
+ # The API version from the webhooks event.
14
+ attr_reader :event_api_version
15
+
16
+ # The API version that this version of the SDK supports.
17
+ attr_reader :sdk_api_version
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,56 @@
1
+ require 'concurrent'
2
+ require 'singleton'
3
+
4
+ module Ingenico::Connect::SDK
5
+ module Webhooks
6
+ # An in-memory secret key store. This implementation can be used
7
+ # in applications where secret keys are specified at application
8
+ # startup. Thread-safe.
9
+ class InMemorySecretKeyStore
10
+
11
+ include Singleton
12
+ include SecretKeyStore
13
+
14
+ # Creates new InMemorySecretKeyStore
15
+ def initialize
16
+ # NOTE: use Map instead of Hash to provide better performance
17
+ # under high concurrency.
18
+ @store = Concurrent::Map.new
19
+ end
20
+
21
+ # Retrieves the secret key corresponding to the given key id
22
+ #
23
+ # key_id:: key id of the secret key
24
+ # Raises {Ingenico::Connect::SDK::Webhooks::SecretKeyNotAvailableException} if the secret key for the given key id is not available.
25
+ def get_secret_key(key_id)
26
+ if (secret_key = @store.get(key_id)).nil?
27
+ msg = "could not find secret key for key id " + key_id
28
+ raise SecretKeyNotAvailableException.new(message: msg, key_id: key_id)
29
+ end
30
+ secret_key
31
+ end
32
+
33
+ # Stores the given secret key for the given key id.
34
+ #
35
+ # key_id:: key id of the secret key
36
+ # secret_id:: the secret key to be stored
37
+ def store_secret_key(key_id, secret_key)
38
+ raise ArgumentError if key_id.nil? or key_id.strip.empty?
39
+ raise ArgumentError if secret_key.nil? or secret_key.strip.empty?
40
+ @store.put(key_id, secret_key)
41
+ end
42
+
43
+ # Removes the secret key for the given key id.
44
+ #
45
+ # key_id:: the key id whose corresponding secret should be removed from the store
46
+ def remove_secret_key(key_id)
47
+ @store.delete(key_id)
48
+ end
49
+
50
+ # Removes all stored secret keys from the store
51
+ def clear
52
+ @store.clear
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,15 @@
1
+ module Ingenico::Connect::SDK
2
+ module Webhooks
3
+ # Raised when an error caused a secret to become not available.
4
+ class SecretKeyNotAvailableException < SignatureValidationException
5
+
6
+ def initialize(args)
7
+ raise ArgumentError if (key_id = args.delete(:key_id)).nil? # key_id is mandatory
8
+ super(args)
9
+ @key_id = key_id
10
+ end
11
+
12
+ attr_reader :key_id
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ module Ingenico::Connect::SDK
2
+ module Webhooks
3
+ # An abstract store of secret keys. Implementation can store secret keys in a database,
4
+ # on disk, etc. Should be Thread-safe.
5
+ module SecretKeyStore
6
+
7
+ # Retrieve secret key for given key id
8
+ #
9
+ # key_id:: given key id
10
+ # Raises {Ingenico::Connect::SDK::Webhooks::SecretKeyNotAvailableException} if the secret key for the given key id is not available.
11
+ def get_secret_key(key_id)
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ module Ingenico::Connect::SDK
2
+ module Webhooks
3
+
4
+ # Raised when an error occurred when validating Webhooks signatures
5
+ class SignatureValidationException < RuntimeError
6
+
7
+ # Creates a new SignatureValidationException
8
+ #
9
+ # @param [Hash] args the options to create the Exception with
10
+ # @option args [String] :message the error message
11
+ # @option args [RuntimeError] :cause an Error object that causes the Exception
12
+ def initialize(args)
13
+ super(args[:message]) # NOTE: can be nil
14
+ # store backtrace info if exception given
15
+ set_backtrace(args[:cause].backtrace) unless args[:cause].nil?
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ module Ingenico::Connect::SDK
2
+ module Webhooks
3
+ # Ingenico ePayments platform factory for several webhooks components
4
+ module Webhooks
5
+
6
+ # Creates a WebhooksHelperBuilder that uses the
7
+ # given SecretkeyStore
8
+ def self.create_helper_builder(secret_key_store)
9
+ WebhooksHelperBuilder.new
10
+ .with_marshaller(DefaultMarshaller.INSTANCE)
11
+ .with_secret_key_store(secret_key_store)
12
+ end
13
+
14
+ # Creates a WebhooksHelper that uses the given
15
+ # SecretkeyStore.
16
+ def self.create_helper(secret_key_store)
17
+ create_helper_builder(secret_key_store).build
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ module Ingenico::Connect::SDK
2
+ module Webhooks
3
+
4
+ class WebhooksEvent < Ingenico::Connect::SDK::DataObject
5
+
6
+ # String
7
+ attr_accessor :api_version
8
+
9
+ # String
10
+ attr_accessor :id
11
+
12
+ # String
13
+ attr_accessor :created
14
+
15
+ # String
16
+ attr_accessor :merchant_id
17
+
18
+ # String
19
+ attr_accessor :type
20
+
21
+ # {Ingenico::Connect::SDK::Domain::Payment::PaymentResponse}
22
+ attr_accessor :payment
23
+
24
+ # {Ingenico::Connect::SDK::Domain::Payout::PayoutResponse}
25
+ attr_accessor :refund
26
+
27
+ # {Ingenico::Connect::SDK::Domain::Refund::RefundResponse}
28
+ attr_accessor :payout
29
+
30
+ # {Ingenico::Connect::SDK::Domain::Token::TokenResponse}
31
+ attr_accessor :token
32
+
33
+ def to_h
34
+ hash = super
35
+ add_to_hash(hash, 'apiVersion', @api_version)
36
+ add_to_hash(hash, 'id', @id)
37
+ add_to_hash(hash, 'created', @created)
38
+ add_to_hash(hash, 'merchantId', @merchant_id)
39
+ add_to_hash(hash, 'type', @type)
40
+ add_to_hash(hash, 'payment', @payment)
41
+ add_to_hash(hash, 'refund', @refund)
42
+ add_to_hash(hash, 'payout', @payout)
43
+ add_to_hash(hash, 'token', @token)
44
+ hash
45
+ end
46
+
47
+ def from_hash(hash)
48
+ super
49
+ @api_version = hash['apiVersion'] if hash.has_key? 'apiVersion'
50
+ @id = hash['id'] if hash.has_key? 'id'
51
+ @created = hash['created'] if hash.has_key? 'created'
52
+ @merchant_id = hash['merchantId'] if hash.has_key? 'merchantId'
53
+ @type = hash['type'] if hash.has_key? 'type'
54
+ @payment = Ingenico::Connect::SDK::Domain::Payment::PaymentResponse.new_from_hash(hash['payment']) if hash.has_key? 'payment'
55
+ @refund = Ingenico::Connect::SDK::Domain::Refund::RefundResponse.new_from_hash(hash['refund']) if hash.has_key? 'refund'
56
+ @payout = Ingenico::Connect::SDK::Domain::Payout::PayoutResponse.new_from_hash(hash['payout']) if hash.has_key? 'payout'
57
+ @token = Ingenico::Connect::SDK::Domain::Token::TokenResponse.new_from_hash(hash['token']) if hash.has_key? 'token'
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,98 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module Ingenico::Connect::SDK
5
+ module Webhooks
6
+
7
+ # Ingenico ePayments platform webhooks Helper, Thread-safe.
8
+ class WebhooksHelper
9
+ def initialize(marshaller, secret_key_store)
10
+ raise ArgumentError if marshaller.nil?
11
+ raise ArgumentError if secret_key_store.nil?
12
+ @marshaller = marshaller
13
+ @secret_key_store = secret_key_store
14
+ end
15
+
16
+ # Unmarshals the given body, while also validating it using the given
17
+ # request headers.
18
+ # body:: body of the request, a String
19
+ # request_headers:: headers of the request, as an Array of {Ingenico::Connect::SDK::RequestHeader}
20
+ def unmarshal(body, request_headers)
21
+ validate(body, request_headers)
22
+ event = @marshaller.unmarshal(body, WebhooksEvent)
23
+ validate_api_version(event)
24
+ event
25
+ end
26
+
27
+ # Validates incoming request using request headers
28
+ #
29
+ # body:: body of the request, a String
30
+ # request_headers:: headers of the request which is an Array of {Ingenico::Connect::SDK::RequestHeader}
31
+ def validate(body, request_headers)
32
+ validate_body(body, request_headers)
33
+ end
34
+
35
+ private
36
+
37
+ HEADER_SIGNATURE = 'X-GCS-Signature'.freeze
38
+ HEADER_KEY_ID = 'X-GCS-KeyId'.freeze
39
+ HMAC_SCHEME = 'SHA256'.freeze
40
+
41
+ # validation utility methods
42
+
43
+ # Validates the body using given request headers
44
+ #
45
+ # body:: a String converted from byte array
46
+ # request_headers:: headers of the request, as an Array of SDK::RequestHeader
47
+ def validate_body(body, request_headers)
48
+ signature = get_header_value(request_headers, HEADER_SIGNATURE)
49
+ key_id = get_header_value(request_headers, HEADER_KEY_ID)
50
+ secret_key = @secret_key_store.get_secret_key(key_id)
51
+ digest = OpenSSL::Digest.new(HMAC_SCHEME)
52
+ hmac = OpenSSL::HMAC.digest(digest, secret_key, body)
53
+ expected_signature = Base64.strict_encode64(hmac).strip
54
+
55
+ unless equal_signatures?(signature, expected_signature)
56
+ msg = "failed to validate signature '#{signature}'"
57
+ raise SignatureValidationException.new(message: msg)
58
+ end
59
+ end
60
+
61
+ # Checks two signatures
62
+ # signature:: a String
63
+ # expected_signature:: a String
64
+ def equal_signatures?(signature, expected_signature)
65
+ # NOTE: copy the signatures to avoid runtime tampering via references
66
+ signature = signature.dup.freeze
67
+ expected_signature = expected_signature.dup.freeze
68
+ # NOTE: do not use simple equality comparision to avoid side channel attack
69
+ limit = [signature.length, expected_signature.length, 256].max
70
+ limit.times.inject(true) do |flag, idx|
71
+ # NOTE: this block is constructed to take constant time to run
72
+ flag &= signature[idx] == expected_signature[idx]
73
+ # [] returns nil if idx >= the length of the String
74
+ end
75
+ end
76
+
77
+ # general utility methods
78
+
79
+ # Returns true if the client API version and the webhooks event API version matches
80
+ def validate_api_version(event)
81
+ raise ApiVersionMismatchException.new(event.api_version, Client.API_VERSION) unless Client.API_VERSION.eql?(event.api_version)
82
+ end
83
+
84
+ # Retrieves header value from the request headers whose header name matches the given parameter
85
+ def get_header_value(request_headers, header_name)
86
+ if (right_header = request_headers.select { |h| h.name.casecmp(header_name) == 0 }).size != 1
87
+ msg = if right_header.empty?
88
+ "could not find header '#{header_name}'"
89
+ else # more than 2 headers
90
+ "encountered multiple occurrences of header '#{header_name}'"
91
+ end
92
+ raise SignatureValidationException.new(message: msg)
93
+ end
94
+ right_header.first.value
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,25 @@
1
+ module Ingenico::Connect::SDK
2
+ module Webhooks
3
+
4
+ # Builder for a WebhooksHelper object.
5
+ class WebhooksHelperBuilder
6
+
7
+ # Sets the Marshaller to use.
8
+ def with_marshaller(marshaller)
9
+ @marshaller = marshaller
10
+ self
11
+ end
12
+
13
+ # Sets the SecretkeyStore to use.
14
+ def with_secret_key_store(secret_key_store)
15
+ @secret_key_store = secret_key_store
16
+ self
17
+ end
18
+
19
+ # Creates a fully initialized WebhooksHelper object
20
+ def build
21
+ WebhooksHelper.new(@marshaller, @secret_key_store)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ {
2
+ "apiVersion": "v1",
3
+ "id": "8ee793f6-4553-4749-85dc-f2ef095c5ab0",
4
+ "created": "2017-02-02T11:25:14.040+0100",
5
+ "merchantId": "20000",
6
+ "type": "payment.paid",
7
+ "payment": {
8
+ "id": "00000200000143570012",
9
+ "paymentOutput": {
10
+ "amountOfMoney": {
11
+ "amount": 1000,
12
+ "currencyCode": "EUR"
13
+ },
14
+ "references": {
15
+ "paymentReference": "200001681810"
16
+ },
17
+ "paymentMethod": "bankTransfer",
18
+ "bankTransferPaymentMethodSpecificOutput": {
19
+ "paymentProductId": 11
20
+ }
21
+ },
22
+ "status": "PAID",
23
+ "statusOutput": {
24
+ "isCancellable": false,
25
+ "statusCategory": "COMPLETED",
26
+ "statusCode": 1000,
27
+ "statusCodeChangeDateTime": "20170202112514",
28
+ "isAuthorized": true
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "apiVersion": "v1",
3
+ "id": "8ee793f6-4553-4749-85dc-f2ef095c5ab0",
4
+ "created": "2017-02-02T11:24:14.040+0100",
5
+ "merchantId": "20000",
6
+ "type": "payment.paid",
7
+ "payment": {
8
+ "id": "00000200000143570012",
9
+ "paymentOutput": {
10
+ "amountOfMoney": {
11
+ "amount": 1000,
12
+ "currencyCode": "EUR"
13
+ },
14
+ "references": {
15
+ "paymentReference": "200001681810"
16
+ },
17
+ "paymentMethod": "bankTransfer",
18
+ "bankTransferPaymentMethodSpecificOutput": {
19
+ "paymentProductId": 11
20
+ }
21
+ },
22
+ "status": "PAID",
23
+ "statusOutput": {
24
+ "isCancellable": false,
25
+ "statusCategory": "COMPLETED",
26
+ "statusCode": 1000,
27
+ "statusCodeChangeDateTime": "20170202112414",
28
+ "isAuthorized": true
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ include Ingenico::Connect::SDK
4
+ describe Webhooks::WebhooksHelper do
5
+
6
+ # define constants for the testbench
7
+ SIGNATURE_HEADER = "X-GCS-Signature"
8
+ SIGNATURE = "2S7doBj/GnJnacIjSJzr5fxGM5xmfQyFAwxv1I53ZEk="
9
+ KEY_ID_HEADER = "X-GCS-KeyId"
10
+ KEY_ID = "dummy-key-id"
11
+ SECRET_KEY = "hello+world"
12
+
13
+ def create_helper(marshaller = DefaultImpl::DefaultMarshaller.INSTANCE)
14
+ Webhooks::WebhooksHelper.new(marshaller, Webhooks::InMemorySecretKeyStore.instance)
15
+ end
16
+
17
+ def read_resource(resource_name)
18
+ prefix = 'spec/fixtures/resources/webhooks/'
19
+ IO.read(prefix + resource_name)
20
+ end
21
+
22
+ before do
23
+ Webhooks::InMemorySecretKeyStore.instance.clear
24
+ end
25
+
26
+ after do
27
+ Webhooks::InMemorySecretKeyStore.instance.clear
28
+ end
29
+
30
+ context 'unmarshal' do
31
+ let(:helper) { create_helper }
32
+ let(:body) { read_resource('valid-body') }
33
+ let(:sig_header) { RequestHeader.new(SIGNATURE_HEADER, SIGNATURE) }
34
+ let(:key_header) { RequestHeader.new(KEY_ID_HEADER, KEY_ID) }
35
+ let(:request_headers) { [sig_header, key_header] }
36
+
37
+ it 'should raise ApiVersionMismatchException when API version does not match' do
38
+ # mock marshaller once to return an event with a wrong API version number
39
+ expect(DefaultImpl::DefaultMarshaller.INSTANCE).to receive(:unmarshal) do |body, klass|
40
+ event = klass.new_from_hash(JSON.load(body))
41
+ event.api_version = 'v0' # wrong version
42
+ event
43
+ end
44
+ Webhooks::InMemorySecretKeyStore.instance.store_secret_key(KEY_ID, SECRET_KEY)
45
+ expect{helper.unmarshal(body, request_headers)}.to raise_error(Webhooks::ApiVersionMismatchException)
46
+ end
47
+
48
+ it 'should raise SecretKeyNotAvailableException when no secret key exists' do
49
+ expect{helper.unmarshal(body, request_headers)}.to raise_error(Webhooks::SecretKeyNotAvailableException)
50
+ end
51
+
52
+ it 'should raise SignatureValidationException when the signature is missing' do
53
+ Webhooks::InMemorySecretKeyStore.instance.store_secret_key(KEY_ID, SECRET_KEY)
54
+ expect{helper.unmarshal(body, [])}.to raise_error(Webhooks::SignatureValidationException)
55
+ end
56
+
57
+ it 'should raise SignatureValidationException when there are duplicate headers' do
58
+ Webhooks::InMemorySecretKeyStore.instance.store_secret_key(KEY_ID, SECRET_KEY)
59
+ request_headers = [sig_header, key_header, sig_header]
60
+ expect{helper.unmarshal(body, request_headers)}.to raise_error(Webhooks::SignatureValidationException)
61
+ end
62
+
63
+ it 'should work when everything is correct' do
64
+ Webhooks::InMemorySecretKeyStore.instance.store_secret_key(KEY_ID, SECRET_KEY)
65
+ event = helper.unmarshal(body, request_headers)
66
+ expect(event.api_version).to eq('v1')
67
+ expect(event.id).to eq("8ee793f6-4553-4749-85dc-f2ef095c5ab0")
68
+ expect(event.created).to eq("2017-02-02T11:24:14.040+0100")
69
+ expect(event.merchant_id).to eq('20000')
70
+ expect(event.type).to eq('payment.paid')
71
+
72
+ expect(event.refund).to be_nil
73
+ expect(event.payout).to be_nil
74
+ expect(event.token).to be_nil
75
+
76
+ expect(event.payment).not_to be_nil
77
+ expect(event.payment.id).to eq("00000200000143570012")
78
+ expect(event.payment.payment_output).not_to be_nil
79
+ expect(event.payment.payment_output.amount_of_money).not_to be_nil
80
+ expect(event.payment.payment_output.amount_of_money.amount).to eq(1000)
81
+ expect(event.payment.payment_output.amount_of_money.currency_code).to eq('EUR')
82
+ expect(event.payment.payment_output.references).not_to be_nil
83
+ expect(event.payment.payment_output.references.payment_reference).to eq("200001681810")
84
+ expect(event.payment.payment_output.payment_method).to eq("bankTransfer")
85
+
86
+ expect(event.payment.payment_output.card_payment_method_specific_output).to be_nil
87
+ expect(event.payment.payment_output.cash_payment_method_specific_output).to be_nil
88
+ expect(event.payment.payment_output.direct_debit_payment_method_specific_output).to be_nil
89
+ expect(event.payment.payment_output.invoice_payment_method_specific_output).to be_nil
90
+ expect(event.payment.payment_output.redirect_payment_method_specific_output).to be_nil
91
+ expect(event.payment.payment_output.sepa_direct_debit_payment_method_specific_output).to be_nil
92
+ expect(event.payment.payment_output.bank_transfer_payment_method_specific_output).not_to be_nil
93
+ expect(event.payment.payment_output.bank_transfer_payment_method_specific_output.payment_product_id).to eq(11)
94
+
95
+ expect(event.payment.status).to eq('PAID')
96
+ expect(event.payment.status_output).not_to be_nil
97
+ expect(event.payment.status_output.is_cancellable).to be false
98
+ expect(event.payment.status_output.status_category).to eq('COMPLETED')
99
+ expect(event.payment.status_output.status_code).to eq(1000)
100
+ expect(event.payment.status_output.status_code_change_date_time).to eq("20170202112414")
101
+ expect(event.payment.status_output.is_authorized).to be true
102
+ end
103
+
104
+ it 'should raise SignatureValidationException when the body is invalid' do
105
+ Webhooks::InMemorySecretKeyStore.instance.store_secret_key(KEY_ID, SECRET_KEY)
106
+ body = read_resource('invalid-body')
107
+ expect{helper.unmarshal(body, request_headers)}.to raise_error(Webhooks::SignatureValidationException)
108
+ end
109
+
110
+ it 'should raise SignatureValidationException when the secret key is invalid' do
111
+ Webhooks::InMemorySecretKeyStore.instance.store_secret_key(KEY_ID, '1'+SECRET_KEY) # wrong key
112
+ expect{helper.unmarshal(body, request_headers)}.to raise_error(Webhooks::SignatureValidationException)
113
+ end
114
+
115
+ it 'should raise SignatureValidationException when the signature is invalid' do
116
+ Webhooks::InMemorySecretKeyStore.instance.store_secret_key(KEY_ID, SECRET_KEY)
117
+ request_headers = [RequestHeader.new(SIGNATURE_HEADER, '1'+SIGNATURE), key_header] # wrong signature
118
+ expect{helper.unmarshal(body, request_headers)}.to raise_error(Webhooks::SignatureValidationException)
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ include Ingenico::Connect::SDK
4
+
5
+ describe Webhooks::Webhooks do
6
+ let(:webhooks_helper) { Webhooks::Webhooks.create_helper(Webhooks::InMemorySecretKeyStore.instance) }
7
+
8
+ context 'construction' do
9
+ it 'uses the default marshaller' do
10
+ expect(webhooks_helper.instance_variable_get(:@marshaller)).to eq(DefaultImpl::DefaultMarshaller.INSTANCE)
11
+ end
12
+
13
+ it 'uses the given key store' do
14
+ expect(webhooks_helper.instance_variable_get(:@secret_key_store)).to eq(Webhooks::InMemorySecretKeyStore.instance)
15
+ end
16
+ end
17
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: connect-sdk-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.8.0
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ingenico ePayments
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-06 00:00:00.000000000 Z
11
+ date: 2017-09-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: concurrent-ruby
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: yard
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -459,6 +473,16 @@ files:
459
473
  - lib/ingenico/connect/sdk/response_header.rb
460
474
  - lib/ingenico/connect/sdk/session.rb
461
475
  - lib/ingenico/connect/sdk/validation_exception.rb
476
+ - lib/ingenico/connect/sdk/webhooks.rb
477
+ - lib/ingenico/connect/sdk/webhooks/api_version_mismatch_exception.rb
478
+ - lib/ingenico/connect/sdk/webhooks/in_memory_secret_key_store.rb
479
+ - lib/ingenico/connect/sdk/webhooks/secret_key_not_available_exception.rb
480
+ - lib/ingenico/connect/sdk/webhooks/secret_key_store.rb
481
+ - lib/ingenico/connect/sdk/webhooks/signature_validation_exception.rb
482
+ - lib/ingenico/connect/sdk/webhooks/webhooks.rb
483
+ - lib/ingenico/connect/sdk/webhooks/webhooks_event.rb
484
+ - lib/ingenico/connect/sdk/webhooks/webhooks_helper.rb
485
+ - lib/ingenico/connect/sdk/webhooks/webhooks_helper_builder.rb
462
486
  - spec/comparable_extension.rb
463
487
  - spec/fixtures/resources/defaultimpl/convertAmount.json
464
488
  - spec/fixtures/resources/defaultimpl/createPayment.failure.invalidCardNumber.json
@@ -487,6 +511,8 @@ files:
487
511
  - spec/fixtures/resources/payment/rejected.json
488
512
  - spec/fixtures/resources/properties.proxy.yml
489
513
  - spec/fixtures/resources/properties.yml
514
+ - spec/fixtures/resources/webhooks/invalid-body
515
+ - spec/fixtures/resources/webhooks/valid-body
490
516
  - spec/integration/connection_pooling_spec.rb
491
517
  - spec/integration/convert_amount_spec.rb
492
518
  - spec/integration/idempotence_spec.rb
@@ -519,6 +545,8 @@ files:
519
545
  - spec/lib/requestparams/find_params_spec.rb
520
546
  - spec/lib/requestparams/get_params_spec.rb
521
547
  - spec/lib/requestparams/param_request_spec.rb
548
+ - spec/lib/webhooks/webhooks_helper_spec.rb
549
+ - spec/lib/webhooks/webhooks_spec.rb
522
550
  - spec/spec_helper.rb
523
551
  homepage: https://github.com/Ingenico-ePayments/connect-sdk-ruby
524
552
  licenses:
@@ -574,6 +602,8 @@ test_files:
574
602
  - spec/fixtures/resources/payment/rejected.json
575
603
  - spec/fixtures/resources/properties.proxy.yml
576
604
  - spec/fixtures/resources/properties.yml
605
+ - spec/fixtures/resources/webhooks/invalid-body
606
+ - spec/fixtures/resources/webhooks/valid-body
577
607
  - spec/integration/connection_pooling_spec.rb
578
608
  - spec/integration/convert_amount_spec.rb
579
609
  - spec/integration/idempotence_spec.rb
@@ -606,4 +636,6 @@ test_files:
606
636
  - spec/lib/requestparams/find_params_spec.rb
607
637
  - spec/lib/requestparams/get_params_spec.rb
608
638
  - spec/lib/requestparams/param_request_spec.rb
639
+ - spec/lib/webhooks/webhooks_helper_spec.rb
640
+ - spec/lib/webhooks/webhooks_spec.rb
609
641
  - spec/spec_helper.rb