barzahlen 1.0.0 → 2.0.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 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: []