barzahlen 1.0.0 → 2.0.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: d22d4228515d62a086a459066ed7d35a5fa596fb
4
- data.tar.gz: add4528a9bb87a690c17609c81831f384bc9a06c
3
+ metadata.gz: ba4379a6be0c31f8284c55d814bf82c97aa4a011
4
+ data.tar.gz: 9f28715903860a7685dc5fc32c0e734322b376ca
5
5
  SHA512:
6
- metadata.gz: bf406f3231504cd7c6047a5df34e3f4253d1a79e4a730323cfd92718f54b7380a9fb8cc90624f37d3d28c2d74a3a727a12a347978a968e1401597caefae24854
7
- data.tar.gz: 5f9ca948df4a0fdb4c032eaf11271b61456b0c89008d7bce56b2bac805b49ebb10f9f7ecf3bf33897bdbfecd3bb9e44a96ddc50a46ff61b4f1c81f80d87b57a3
6
+ metadata.gz: 399821910a344a8568298987061ba410fc5a4917c89f0dcad08c8e395570546c2eca6f3f2abd5c99186221d7ae0e1db5fef121354f3cd0f7504ad3d8c8c671f0
7
+ data.tar.gz: 671aa7a285cbda33132585721adcee37c8002067dc18e2d7da076d8266ca518d41af087b0c9d74a22dfc9b47ea5080aa0f1df0fec618afd46a93ea4dfbc0ff1a
@@ -0,0 +1,7 @@
1
+ require "grac"
2
+
3
+ require_relative "./barzahlen/version"
4
+ require_relative "./barzahlen/error"
5
+ require_relative "./barzahlen/configuration"
6
+ require_relative "./barzahlen/middleware"
7
+ require_relative "./barzahlen/slip"
@@ -0,0 +1,32 @@
1
+ module Barzahlen
2
+ class Configuration
3
+ API_HOST = "https://api.barzahlen.de/v2"
4
+ API_HOST_SANDBOX = "https://api-sandbox.barzahlen.de/v2"
5
+
6
+ attr_accessor :sandbox
7
+ attr_accessor :division_id
8
+ attr_accessor :payment_key
9
+
10
+ def initialize
11
+ @sandbox = false
12
+ @division_id = "not_valid_division_id"
13
+ @payment_key = "not_valid_payment_key"
14
+ end
15
+ end
16
+
17
+ class << self
18
+ attr_accessor :configuration
19
+
20
+ def configuration
21
+ @configuration ||= Configuration.new
22
+ end
23
+
24
+ def reset
25
+ @configuration = Configuration.new
26
+ end
27
+
28
+ def configure
29
+ yield(configuration)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,116 @@
1
+ require "json"
2
+
3
+ module Barzahlen
4
+ module Error
5
+ class ClientError < StandardError
6
+ attr_reader :error_message
7
+
8
+ def initialize(error_message)
9
+ @error_message = error_message
10
+ end
11
+
12
+ def message
13
+ return @error_message
14
+ end
15
+
16
+ alias_method :to_s, :message
17
+ end
18
+
19
+ class SignatureError < ClientError; end
20
+
21
+ class ApiError < StandardError
22
+ attr_reader :error_class
23
+ attr_reader :error_code
24
+ attr_reader :error_message
25
+ attr_reader :documentation_url
26
+ attr_reader :request_id
27
+
28
+ def initialize(error_hash = {})
29
+ @error_class = error_hash[:error_class]
30
+ @error_code = error_hash[:error_code]
31
+ @error_message = error_hash[:message]
32
+ @documentation_url = error_hash[:documentation_url]
33
+ @request_id = error_hash[:request_id]
34
+ end
35
+
36
+ def message
37
+ return "Error occurred with: #{@error_message}"
38
+ end
39
+
40
+ alias_method :to_s, :message
41
+ end
42
+
43
+ class AuthError < ApiError; end
44
+ class TransportError < ApiError; end
45
+ class IdempotencyError < ApiError; end
46
+ class RateLimitError < ApiError; end
47
+ class InvalidFormatError < ApiError; end
48
+ class InvalidStateError < ApiError; end
49
+ class InvalidParameterError < ApiError; end
50
+ class NotAllowedError < ApiError; end
51
+ class ServerError < ApiError; end
52
+ class UnexpectedError < ApiError; end
53
+
54
+ # This generates ApiErrors based on the response error classes of CPS
55
+ def self.generate_error_from_response(response_body)
56
+ error_hash = generate_error_hash_with_symbols(response_body)
57
+
58
+ case error_hash[:error_class]
59
+ when "auth"
60
+ return AuthError.new( error_hash )
61
+ when "transport"
62
+ return TransportError.new( error_hash )
63
+ when "idempotency"
64
+ return IdempotencyError.new( error_hash )
65
+ when "rate_limit"
66
+ return RateLimitError.new( error_hash )
67
+ when "invalid_format"
68
+ return InvalidFormatError.new( error_hash )
69
+ when "invalid_state"
70
+ return InvalidStateError.new( error_hash )
71
+ when "invalid_parameter"
72
+ return InvalidParameterError.new( error_hash )
73
+ when "not_allowed"
74
+ return NotAllowedError.new( error_hash )
75
+ when "server_error"
76
+ return ServerError.new( error_hash )
77
+ else
78
+ return UnexpectedError.new( error_hash )
79
+ end
80
+ end
81
+
82
+
83
+ private
84
+
85
+ def self.parse_json(json)
86
+ begin
87
+ hash = JSON.parse(json)
88
+ rescue JSON::ParserError => e
89
+ return nil
90
+ end
91
+ self.symbolize_keys(hash)
92
+ end
93
+
94
+ def self.generate_error_hash_with_symbols(body)
95
+ if body.is_a?(Hash)
96
+ error_hash = self.symbolize_keys(body)
97
+ elsif body.is_a?(String)
98
+ error_hash = parse_json(body) || {}
99
+ else
100
+ error_hash = Hash.new
101
+ end
102
+
103
+ error_hash[:error_class] ||= "Unexpected_Error"
104
+ error_hash[:error_code] ||= "Unknown error code (body): \"#{body.to_s}\""
105
+ error_hash[:message] ||= "Please contact CPS to help us fix that as soon as possible."
106
+ error_hash[:documentation_url] ||= "https://www.cashpaymentsolutions.com/de/geschaeftskunden/kontakt"
107
+ error_hash[:request_id] ||= "not_available"
108
+
109
+ error_hash
110
+ end
111
+
112
+ def self.symbolize_keys(hash)
113
+ Hash[hash.map{ |k, v| [k.to_sym, v] }]
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,69 @@
1
+ require "openssl"
2
+ require "uri"
3
+ require "time"
4
+
5
+ module Barzahlen
6
+ module Middleware
7
+ class Signature
8
+ def initialize(request, config)
9
+ @request = request
10
+ @config = config
11
+ end
12
+
13
+ def call (opts, request_uri, method, params, body)
14
+ parsed_uri = URI.parse(request_uri)
15
+ request_host_header = parsed_uri.host + ":" + parsed_uri.port.to_s
16
+ request_method = method
17
+ request_host_path = parsed_uri.path
18
+ request_query_string = URI.encode_www_form(params)
19
+ request_idempotency_key = opts[:headers]["Idempotency-Key"]
20
+ request_date_header = Time.now.utc.strftime("%a, %d %b %Y %H:%M:%S GMT")
21
+
22
+ signature = Barzahlen::Middleware.generate_bz_signature(
23
+ @config.payment_key,
24
+ request_host_header,
25
+ request_method,
26
+ request_date_header,
27
+ request_host_path,
28
+ request_query_string,
29
+ body,
30
+ request_idempotency_key
31
+ )
32
+
33
+ # Attach the Date, Authorization and Host to the request
34
+ new_headers = opts[:headers].merge(
35
+ {
36
+ Date: request_date_header,
37
+ Authorization: "BZ1-HMAC-SHA256 DivisionId=#{@config.division_id}, Signature=#{signature}",
38
+ Host: request_host_header,
39
+ }
40
+ )
41
+
42
+ return @request.call({headers: new_headers}, request_uri, method, params, body)
43
+ end
44
+ end
45
+
46
+ def self.generate_bz_signature(
47
+ payment_key,
48
+ request_host_header,
49
+ request_method,
50
+ request_date_header,
51
+ request_host_path = "",
52
+ request_query_string = "",
53
+ request_body = "",
54
+ request_idempotency_key = "")
55
+
56
+ request_body_digest = OpenSSL::Digest.hexdigest("SHA256", request_body.to_s || "")
57
+
58
+ raw_signature = "#{request_host_header}\n"\
59
+ "#{request_method.upcase}\n"\
60
+ "#{request_host_path}\n"\
61
+ "#{request_query_string}\n"\
62
+ "#{request_date_header}\n"\
63
+ "#{request_idempotency_key}\n"\
64
+ "#{request_body_digest}"
65
+
66
+ OpenSSL::HMAC.hexdigest("SHA256", payment_key, raw_signature)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,113 @@
1
+ require "securerandom"
2
+ require "json"
3
+
4
+ module Barzahlen
5
+ IDEMPOTENCY_ENABLED = true
6
+
7
+ # For idempotency purposes a class takes care of refund and payment
8
+
9
+ class CreateSlipRequest
10
+ def initialize(opts = {})
11
+ @request = Barzahlen.get_grac_client(Barzahlen::IDEMPOTENCY_ENABLED)
12
+ @request_hash = opts
13
+ end
14
+
15
+ def send
16
+ @request_hash.freeze
17
+ @request_hash.each do |key, value|
18
+ @request_hash[key].freeze
19
+ end
20
+ Barzahlen.execute_with_error_handling do
21
+ @request.path("/slips").post(@request_hash)
22
+ end
23
+ end
24
+ end
25
+
26
+ # If idempotency is not important a simple request is more than enough
27
+
28
+ def self.retrieve_slip(slip_id)
29
+ self.execute_with_error_handling do
30
+ self.get_grac_client.path("/slips/{id}", id: slip_id.to_s).get
31
+ end
32
+ end
33
+
34
+ def self.update_slip(slip_id, opts = {})
35
+ self.execute_with_error_handling do
36
+ self.get_grac_client.path("/slips/{id}", id: slip_id.to_s).patch(opts)
37
+ end
38
+ end
39
+
40
+ def self.resend_email(slip_id)
41
+ self.execute_with_error_handling do
42
+ self.get_grac_client.path("/slips/{id}/resend/email", id: slip_id.to_s).post
43
+ end
44
+ end
45
+
46
+ def self.resend_text_message(slip_id)
47
+ self.execute_with_error_handling do
48
+ self.get_grac_client.path("/slips/{id}/resend/text_message", id: slip_id.to_s).post
49
+ end
50
+ end
51
+
52
+ def self.invalidate_slip(slip_id)
53
+ self.execute_with_error_handling do
54
+ self.get_grac_client.path("/slips/{id}/invalidate", id: slip_id.to_s).post
55
+ end
56
+ end
57
+
58
+ # Handle a webhook request
59
+
60
+ def self.webhook_request(request)
61
+ bz_hook_format = request["Bz-Hook-Format"]
62
+
63
+ #stop processing when bz-hook-format = v1 because it will be or was send as v2
64
+ if bz_hook_format.include? "v1"
65
+ return nil
66
+ end
67
+
68
+ signature = Barzahlen::Middleware.generate_bz_signature(
69
+ Barzahlen.configuration.payment_key,
70
+ request["Host"] + ":" + (request["Port"] || "443"),
71
+ request["Method"] ? request["Method"].upcase : "POST",
72
+ request["Date"],
73
+ request["Path"].split("?")[0] || request["Path"],
74
+ request["Path"].split("?")[1] || "",
75
+ request["Body"]
76
+ )
77
+
78
+ if request["Bz-Signature"].include? signature
79
+ return JSON.parse(request["Body"])
80
+ else
81
+ raise Barzahlen::Error::SignatureError.new("Signature not valid")
82
+ end
83
+ end
84
+
85
+
86
+ private
87
+
88
+ @@grac_client = nil
89
+
90
+ def self.get_grac_client(idempotency = false)
91
+ @@grac_client ||= Grac::Client.new(
92
+ Barzahlen.configuration.sandbox ?
93
+ Barzahlen::Configuration::API_HOST_SANDBOX : Barzahlen::Configuration::API_HOST,
94
+ middleware: [ [ Barzahlen::Middleware::Signature, Barzahlen.configuration ] ]
95
+ )
96
+
97
+ if idempotency
98
+ return @@grac_client.set( headers: { "Idempotency-Key" => SecureRandom.uuid} )
99
+ else
100
+ return @@grac_client
101
+ end
102
+ end
103
+
104
+ def self.execute_with_error_handling
105
+ begin
106
+ yield
107
+ rescue Grac::Exception::RequestFailed => e
108
+ raise Barzahlen::Error.generate_error_from_response("")
109
+ rescue Grac::Exception::ClientException => e
110
+ raise Barzahlen::Error.generate_error_from_response(e.body)
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,3 @@
1
+ module Barzahlen
2
+ VERSION = "2.0.0"
3
+ end
metadata CHANGED
@@ -1,126 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: barzahlen
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Mathias Hertlein
7
+ - David Leib
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-04-03 00:00:00.000000000 Z
11
+ date: 2016-04-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: bundler
14
+ name: rake
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '1.3'
20
- - - ">="
21
- - !ruby/object:Gem::Version
22
- version: 1.3.5
19
+ version: 10.4.1
23
20
  type: :development
24
21
  prerelease: false
25
22
  version_requirements: !ruby/object:Gem::Requirement
26
23
  requirements:
27
24
  - - "~>"
28
25
  - !ruby/object:Gem::Version
29
- version: '1.3'
30
- - - ">="
31
- - !ruby/object:Gem::Version
32
- version: 1.3.5
26
+ version: 10.4.1
33
27
  - !ruby/object:Gem::Dependency
34
- name: rake
28
+ name: rspec
35
29
  requirement: !ruby/object:Gem::Requirement
36
30
  requirements:
37
31
  - - "~>"
38
32
  - !ruby/object:Gem::Version
39
- version: '10.0'
40
- - - ">="
41
- - !ruby/object:Gem::Version
42
- version: 10.0.4
33
+ version: 3.2.0
43
34
  type: :development
44
35
  prerelease: false
45
36
  version_requirements: !ruby/object:Gem::Requirement
46
37
  requirements:
47
38
  - - "~>"
48
39
  - !ruby/object:Gem::Version
49
- version: '10.0'
50
- - - ">="
51
- - !ruby/object:Gem::Version
52
- version: 10.0.4
40
+ version: 3.2.0
53
41
  - !ruby/object:Gem::Dependency
54
- name: rspec
42
+ name: rack-test
55
43
  requirement: !ruby/object:Gem::Requirement
56
44
  requirements:
57
45
  - - "~>"
58
46
  - !ruby/object:Gem::Version
59
- version: '2.14'
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: 2.14.1
47
+ version: 0.6.3
63
48
  type: :development
64
49
  prerelease: false
65
50
  version_requirements: !ruby/object:Gem::Requirement
66
51
  requirements:
67
52
  - - "~>"
68
53
  - !ruby/object:Gem::Version
69
- version: '2.14'
70
- - - ">="
71
- - !ruby/object:Gem::Version
72
- version: 2.14.1
54
+ version: 0.6.3
73
55
  - !ruby/object:Gem::Dependency
74
- name: builder
56
+ name: grac
75
57
  requirement: !ruby/object:Gem::Requirement
76
58
  requirements:
77
59
  - - "~>"
78
60
  - !ruby/object:Gem::Version
79
- version: '3.0'
80
- - - ">="
81
- - !ruby/object:Gem::Version
82
- version: 3.0.0
83
- type: :development
61
+ version: 2.2.1
62
+ type: :runtime
84
63
  prerelease: false
85
64
  version_requirements: !ruby/object:Gem::Requirement
86
65
  requirements:
87
66
  - - "~>"
88
67
  - !ruby/object:Gem::Version
89
- version: '3.0'
90
- - - ">="
91
- - !ruby/object:Gem::Version
92
- version: 3.0.0
93
- description: The official Ruby-SDK for accessing the Barzalen Payment Service
68
+ version: 2.2.1
69
+ description: This is a ruby gem to access the Barzahlen API v2.
94
70
  email:
95
- - mathias.hertlein@barzahlen.de
96
- executables:
97
- - test.rb
71
+ - david.leib@barzahlen.de
72
+ executables: []
98
73
  extensions: []
99
74
  extra_rdoc_files: []
100
75
  files:
101
- - Gemfile
102
- - README.md
103
- - Rakefile
104
- - bin/test.rb
105
- - lib/barzahlen/api/online.rb
106
- - lib/barzahlen/api/online/api.rb
107
- - lib/barzahlen/api/online/hash_builder.rb
108
- - lib/barzahlen/api/online/http.rb
109
- - lib/barzahlen/api/online/notification_handler.rb
110
- - lib/barzahlen/api/online/response_parser.rb
111
- - lib/barzahlen/api/online/transaction.rb
112
- - lib/barzahlen/api/online/version.rb
113
- - lib/ruby_sdk.rb
114
- - lib/ruby_sdk/version.rb
115
- - spec/barzahlen/api/online/api_spec.rb
116
- - spec/barzahlen/api/online/hash_builder_spec.rb
117
- - spec/barzahlen/api/online/http_spec.rb
118
- - spec/barzahlen/api/online/notification_handler_spec.rb
119
- - spec/barzahlen/api/online/response_parser_spec.rb
120
- - spec/barzahlen/api/online/transaction_spec.rb
121
- - spec/barzahlen/api/online_spec.rb
122
- - spec/spec_helper.rb
123
- homepage: https://www.barzahlen.de/
76
+ - lib/barzahlen.rb
77
+ - lib/barzahlen/configuration.rb
78
+ - lib/barzahlen/error.rb
79
+ - lib/barzahlen/middleware.rb
80
+ - lib/barzahlen/slip.rb
81
+ - lib/barzahlen/version.rb
82
+ homepage: ''
124
83
  licenses:
125
84
  - MIT
126
85
  metadata: {}
@@ -143,13 +102,5 @@ rubyforge_project:
143
102
  rubygems_version: 2.2.2
144
103
  signing_key:
145
104
  specification_version: 4
146
- summary: Barzahlen Ruby-SDK
147
- test_files:
148
- - spec/barzahlen/api/online/api_spec.rb
149
- - spec/barzahlen/api/online/hash_builder_spec.rb
150
- - spec/barzahlen/api/online/http_spec.rb
151
- - spec/barzahlen/api/online/notification_handler_spec.rb
152
- - spec/barzahlen/api/online/response_parser_spec.rb
153
- - spec/barzahlen/api/online/transaction_spec.rb
154
- - spec/barzahlen/api/online_spec.rb
155
- - spec/spec_helper.rb
105
+ summary: Client gem for API Barzahlen v2.
106
+ test_files: []