eml 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +10 -0
  3. data/.env.example +6 -0
  4. data/.gitignore +5 -0
  5. data/.rspec +5 -0
  6. data/.rubocop.yml +90 -0
  7. data/Gemfile +6 -0
  8. data/LICENSE +21 -0
  9. data/README.md +87 -0
  10. data/eml.gemspec +37 -0
  11. data/lib/eml.rb +22 -0
  12. data/lib/eml/config.rb +49 -0
  13. data/lib/eml/data/countries.rb +260 -0
  14. data/lib/eml/data/currencies.rb +176 -0
  15. data/lib/eml/data/states.rb +4055 -0
  16. data/lib/eml/environment.rb +54 -0
  17. data/lib/eml/error.rb +25 -0
  18. data/lib/eml/error/rest.rb +42 -0
  19. data/lib/eml/error/rest/authentication.rb +21 -0
  20. data/lib/eml/error/rest/bad_request.rb +20 -0
  21. data/lib/eml/error/rest/daily_funding_limit.rb +19 -0
  22. data/lib/eml/error/rest/forbidden.rb +20 -0
  23. data/lib/eml/error/rest/internal_server.rb +9 -0
  24. data/lib/eml/error/rest/not_found.rb +20 -0
  25. data/lib/eml/error/rest/unprocessable_entity.rb +20 -0
  26. data/lib/eml/lib/endpoint_class.rb +42 -0
  27. data/lib/eml/parameters.rb +153 -0
  28. data/lib/eml/payload.rb +137 -0
  29. data/lib/eml/response.rb +103 -0
  30. data/lib/eml/uk.rb +51 -0
  31. data/lib/eml/uk/api_resource.rb +94 -0
  32. data/lib/eml/uk/config.rb +52 -0
  33. data/lib/eml/uk/lib/endpoint_class.rb +19 -0
  34. data/lib/eml/uk/lib/parse_date.rb +35 -0
  35. data/lib/eml/uk/model/transaction.rb +56 -0
  36. data/lib/eml/uk/parameters.rb +99 -0
  37. data/lib/eml/uk/parameters/agreement/show.rb +22 -0
  38. data/lib/eml/uk/parameters/card/activation.rb +24 -0
  39. data/lib/eml/uk/parameters/card/lock.rb +26 -0
  40. data/lib/eml/uk/parameters/card/register.rb +24 -0
  41. data/lib/eml/uk/parameters/card/reload.rb +24 -0
  42. data/lib/eml/uk/parameters/card/show.rb +102 -0
  43. data/lib/eml/uk/parameters/card/transaction.rb +59 -0
  44. data/lib/eml/uk/parameters/card/unload.rb +26 -0
  45. data/lib/eml/uk/parameters/card/unlock.rb +26 -0
  46. data/lib/eml/uk/parameters/card/void.rb +26 -0
  47. data/lib/eml/uk/payload.rb +94 -0
  48. data/lib/eml/uk/payload/agreement/show.rb +46 -0
  49. data/lib/eml/uk/payload/card/activation.rb +73 -0
  50. data/lib/eml/uk/payload/card/lock.rb +34 -0
  51. data/lib/eml/uk/payload/card/register.rb +70 -0
  52. data/lib/eml/uk/payload/card/reload.rb +38 -0
  53. data/lib/eml/uk/payload/card/show.rb +12 -0
  54. data/lib/eml/uk/payload/card/transaction.rb +12 -0
  55. data/lib/eml/uk/payload/card/unload.rb +38 -0
  56. data/lib/eml/uk/payload/card/unlock.rb +23 -0
  57. data/lib/eml/uk/payload/card/void.rb +23 -0
  58. data/lib/eml/uk/payload/contactentity.rb +64 -0
  59. data/lib/eml/uk/payload/iso.rb +48 -0
  60. data/lib/eml/uk/payload/location.rb +30 -0
  61. data/lib/eml/uk/resources/agreement.rb +32 -0
  62. data/lib/eml/uk/resources/card.rb +181 -0
  63. data/lib/eml/uk/response.rb +29 -0
  64. data/lib/eml/uk/responses/agreement/show.rb +16 -0
  65. data/lib/eml/uk/responses/card/reload.rb +19 -0
  66. data/lib/eml/uk/responses/card/show.rb +53 -0
  67. data/lib/eml/uk/responses/card/transaction.rb +24 -0
  68. data/lib/eml/version.rb +6 -0
  69. data/sorbet/config +2 -0
  70. data/sorbet/rbi/gems/addressable.rbi +198 -0
  71. data/sorbet/rbi/gems/ast.rbi +47 -0
  72. data/sorbet/rbi/gems/concurrent-ruby.rbi +218 -0
  73. data/sorbet/rbi/gems/crack.rbi +47 -0
  74. data/sorbet/rbi/gems/docile.rbi +31 -0
  75. data/sorbet/rbi/gems/domain_name.rbi +51 -0
  76. data/sorbet/rbi/gems/dotenv.rbi +67 -0
  77. data/sorbet/rbi/gems/faker.rbi +1350 -0
  78. data/sorbet/rbi/gems/hashdiff.rbi +65 -0
  79. data/sorbet/rbi/gems/http-cookie.rbi +92 -0
  80. data/sorbet/rbi/gems/http-form_data.rbi +73 -0
  81. data/sorbet/rbi/gems/http.rbi +609 -0
  82. data/sorbet/rbi/gems/http_parser.rb.rbi +36 -0
  83. data/sorbet/rbi/gems/i18n.rbi +191 -0
  84. data/sorbet/rbi/gems/jaro_winkler.rbi +14 -0
  85. data/sorbet/rbi/gems/parallel.rbi +81 -0
  86. data/sorbet/rbi/gems/parser.rbi +835 -0
  87. data/sorbet/rbi/gems/public_suffix.rbi +103 -0
  88. data/sorbet/rbi/gems/rainbow.rbi +117 -0
  89. data/sorbet/rbi/gems/rspec-core.rbi +1655 -0
  90. data/sorbet/rbi/gems/rspec-support.rbi +126 -0
  91. data/sorbet/rbi/gems/rspec.rbi +14 -0
  92. data/sorbet/rbi/gems/rubocop-performance.rbi +276 -0
  93. data/sorbet/rbi/gems/rubocop-rspec.rbi +860 -0
  94. data/sorbet/rbi/gems/rubocop.rbi +6943 -0
  95. data/sorbet/rbi/gems/ruby-progressbar.rbi +304 -0
  96. data/sorbet/rbi/gems/simplecov-html.rbi +30 -0
  97. data/sorbet/rbi/gems/simplecov.rbi +225 -0
  98. data/sorbet/rbi/gems/unf.rbi +18 -0
  99. data/sorbet/rbi/gems/unicode-display_width.rbi +16 -0
  100. data/sorbet/rbi/gems/vcr.rbi +503 -0
  101. data/sorbet/rbi/gems/webmock.rbi +521 -0
  102. data/sorbet/rbi/hidden-definitions/errors.txt +4802 -0
  103. data/sorbet/rbi/hidden-definitions/hidden.rbi +10700 -0
  104. data/sorbet/rbi/sorbet-typed/lib/bundler/all/bundler.rbi +8682 -0
  105. data/sorbet/rbi/sorbet-typed/lib/ruby/all/gem.rbi +4222 -0
  106. data/sorbet/rbi/sorbet-typed/lib/ruby/all/open3.rbi +111 -0
  107. data/sorbet/rbi/sorbet-typed/lib/ruby/all/resolv.rbi +543 -0
  108. data/sorbet/rbi/todo.rbi +24 -0
  109. data/spec/config_spec.rb +26 -0
  110. data/spec/helpers/config_helper.rb +21 -0
  111. data/spec/helpers/vcr_helper.rb +19 -0
  112. data/spec/spec_helper.rb +120 -0
  113. data/spec/uk/api_resource_spec.rb +39 -0
  114. data/spec/uk/resources/agreement_spec.rb +23 -0
  115. data/spec/uk/resources/card_spec.rb +197 -0
  116. metadata +339 -0
@@ -0,0 +1,137 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ # A payload is the data sent in the body of a request such as POST or PUT
5
+ module EML
6
+ class Payload
7
+ extend T::Sig
8
+
9
+ sig { params(payload: T::Hash[Symbol, T.untyped]).void }
10
+ def initialize(payload)
11
+ assign_params(payload)
12
+ validate_required_payload_presence
13
+ end
14
+
15
+ ENDPOINT_CLASS_TYPE = "Payload"
16
+
17
+ class << self
18
+ extend T::Sig
19
+
20
+ sig do
21
+ params(
22
+ resource_class: T.untyped,
23
+ endpoint: String,
24
+ payload: T::Hash[Symbol, T.untyped]
25
+ ).returns(T.untyped)
26
+ end
27
+ def convert(resource_class, endpoint, payload)
28
+ endpoint_class = EML::EndpointClass.(
29
+ class_type: ENDPOINT_CLASS_TYPE,
30
+ resource_class: resource_class,
31
+ endpoint: endpoint
32
+ )
33
+
34
+ convert_with_endpoint_class(
35
+ endpoint: endpoint, endpoint_class: endpoint_class,
36
+ payload: payload, resource_class: resource_class
37
+ )
38
+ end
39
+
40
+ protected
41
+
42
+ sig do
43
+ params(
44
+ endpoint_class: T.nilable(T.class_of(EML::Payload)),
45
+ resource_class: T.untyped,
46
+ endpoint: String,
47
+ payload: T::Hash[Symbol, T.untyped]
48
+ ).returns(T.untyped)
49
+ end
50
+ def convert_with_endpoint_class(
51
+ endpoint_class:, resource_class:, endpoint:, payload:
52
+ )
53
+ return endpoint_class.new(payload) unless endpoint_class.nil?
54
+ return payload if payload.empty?
55
+
56
+ message = "No endpoint class can be found for resource, " \
57
+ "#{resource_class}, with endpoint #{endpoint}"
58
+ raise ArgumentError, message
59
+ end
60
+ end
61
+
62
+ sig { returns(T::Hash[Symbol, T.untyped]) }
63
+ def to_h
64
+ instance_variables.each_with_object({}) do |variable_name, params|
65
+ key = variable_name.to_s[1..-1].to_sym
66
+ params[key] = instance_variable_get(variable_name)
67
+ params[key] = params[key].to_h if params[key].respond_to?(:to_h)
68
+ end
69
+ end
70
+
71
+ protected
72
+
73
+ sig do
74
+ params(param_name: Symbol, param_value: String, values: T::Array[String]).
75
+ void
76
+ end
77
+ def validate_enum(param_name, param_value, values)
78
+ return if values.include?(param_value)
79
+
80
+ value_message = values.dup.
81
+ tap { |vals| vals[-1] = "or #{vals.last}" }.
82
+ join(", ")
83
+ raise(ArgumentError, "#{param_name} must be one of #{value_message}")
84
+ end
85
+
86
+ sig do
87
+ params(param_name: Symbol, param_value: String, length: Integer).void
88
+ end
89
+ def validate_max_length(param_name, param_value, length)
90
+ return if param_value.to_s.length <= length
91
+
92
+ raise(
93
+ ArgumentError,
94
+ "#{param_name} is expected to be no more than #{length} characters"
95
+ )
96
+ end
97
+
98
+ private
99
+
100
+ sig { params(params: T::Hash[Symbol, T.untyped]).void }
101
+ def assign_params(params)
102
+ params.each do |name, value|
103
+ __send__(:"#{name}=", value)
104
+ end
105
+ end
106
+
107
+ sig { void }
108
+ def validate_required_payload_presence
109
+ return unless self.class.const_defined?(:REQUIRED_VALUES)
110
+
111
+ required_params = self.class.const_get(:REQUIRED_VALUES)
112
+ required_params.each do |param_name|
113
+ validate_required_payload_value_presence(param_name)
114
+ end
115
+ end
116
+
117
+ # rubocop:disable Metrics/MethodLength
118
+ sig { params(value_key: Symbol, object: EML::Payload).void }
119
+ def validate_required_payload_value_presence(value_key, object: self)
120
+ names = value_key.to_s.split(".")
121
+ value = object.instance_variable_get(:"@#{names.shift}")
122
+
123
+ if value.nil?
124
+ raise(
125
+ ArgumentError,
126
+ "#{value_key} was not supplied but it is a required field"
127
+ )
128
+ end
129
+
130
+ if names.length.positive?
131
+ value_key = names.join(".").to_sym
132
+ validate_required_payload_value_presence(value_key, object: value)
133
+ end
134
+ end
135
+ # rubocop:enable Metrics/MethodLength
136
+ end
137
+ end
@@ -0,0 +1,103 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ class Response
6
+ extend T::Sig
7
+
8
+ sig { params(field_name: Symbol).void }
9
+ def self.field(field_name)
10
+ define_method(field_name) do
11
+ string_name = field_name.to_s
12
+ body[string_name]
13
+ end
14
+ end
15
+
16
+ sig { params(response: HTTP::Response, id: T.nilable(String)).void }
17
+ def initialize(response, id: nil)
18
+ @response = T.let(response, HTTP::Response)
19
+ @body = T.let(nil, T.nilable(String))
20
+ @http_status = T.let(nil, T.nilable(Integer))
21
+ @url = T.let(nil, T.nilable(String))
22
+ raise_error(id: id) unless http_status == 200
23
+ end
24
+
25
+ # TODO: sometimes we get a string and sometimes a hash. When it's a hash,
26
+ # there should be a custom response object
27
+ sig { returns(T.untyped) }
28
+ def body
29
+ @body ||= JSON.parse(@response.body.to_s)
30
+ rescue JSON::ParserError
31
+ @body = ""
32
+ end
33
+
34
+ sig { returns(T.nilable(String)) }
35
+ def error
36
+ body.fetch("message", nil) if body.respond_to?(:fetch)
37
+ end
38
+
39
+ sig { returns(T::Hash[Symbol, String]) }
40
+ def headers
41
+ @response.headers
42
+ end
43
+
44
+ sig { returns(Integer) }
45
+ def http_status
46
+ @http_status ||= @response.status.code
47
+ end
48
+
49
+ sig { returns(T::Boolean) }
50
+ def success?
51
+ http_status == 200
52
+ end
53
+
54
+ sig { returns(String) }
55
+ def url
56
+ @url ||= T.unsafe(@response).url.to_s
57
+ end
58
+
59
+ private
60
+
61
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
62
+ sig { params(id: T.nilable(String)).returns(EML::RESTError) }
63
+ def raise_error(id: nil)
64
+ check_for_incorrectly_categorised_errors
65
+
66
+ case http_status
67
+ when 400
68
+ raise T.unsafe(EML::REST::BadRequestError).new(error, self)
69
+ when 401
70
+ raise T.unsafe(EML::REST::AuthenticationError).new(nil, self)
71
+ when 403
72
+ raise T.unsafe(EML::REST::ForbiddenError).new(nil, self)
73
+ when 404
74
+ raise T.unsafe(EML::REST::NotFoundError).new(nil, self, id: T.must(id))
75
+ when 500
76
+ process_internal_server_error
77
+ raise T.unsafe(EML::REST::InternalServerError).new(error, self)
78
+ end
79
+
80
+ raise T.unsafe(EML::RESTError).
81
+ new("Unknown error (#{http_status}): #{error}", self)
82
+ end
83
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
84
+
85
+ sig { returns(T.nilable(EML::RESTError)) }
86
+ def check_for_incorrectly_categorised_errors
87
+ if error.match?(/is not in (the proper|a valid) status/)
88
+ raise T.unsafe(EML::REST::UnprocessableEntityError).new(error, self)
89
+ end
90
+ end
91
+
92
+ sig { returns(T.nilable(EML::RESTError)) }
93
+ def process_internal_server_error
94
+ case error
95
+ when "Card reload could not be completed because it violated the " \
96
+ "funding limit: 'Daily Max Reload Amount Funding Limit'"
97
+ raise T.unsafe(EML::REST::DailyFundingLimitError).new(nil, self)
98
+ when "Invalid username or password."
99
+ raise T.unsafe(EML::REST::AuthenticationError).new(nil, self)
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,51 @@
1
+ # typed: strong
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ module UK
6
+ end
7
+ end
8
+
9
+ require "eml/uk/api_resource"
10
+ require "eml/uk/config"
11
+
12
+ require "eml/uk/lib/endpoint_class"
13
+ require "eml/uk/lib/parse_date"
14
+
15
+ require "eml/uk/model/transaction"
16
+
17
+ require "eml/uk/parameters"
18
+ require "eml/uk/parameters/agreement/show"
19
+ require "eml/uk/parameters/card/activation"
20
+ require "eml/uk/parameters/card/show"
21
+ require "eml/uk/parameters/card/lock"
22
+ require "eml/uk/parameters/card/register"
23
+ require "eml/uk/parameters/card/reload"
24
+ require "eml/uk/parameters/card/transaction"
25
+ require "eml/uk/parameters/card/unload"
26
+ require "eml/uk/parameters/card/unlock"
27
+
28
+ require "eml/uk/payload"
29
+ require "eml/uk/payload/agreement/show"
30
+ require "eml/uk/payload/iso"
31
+ require "eml/uk/payload/contactentity"
32
+ require "eml/uk/payload/location"
33
+ require "eml/uk/payload/nil"
34
+ require "eml/uk/payload/card/activation"
35
+ require "eml/uk/payload/card/show"
36
+ require "eml/uk/payload/card/lock"
37
+ require "eml/uk/payload/card/register"
38
+ require "eml/uk/payload/card/reload"
39
+ require "eml/uk/payload/card/transaction"
40
+ require "eml/uk/payload/card/unload"
41
+ require "eml/uk/payload/card/unlock"
42
+ require "eml/uk/payload/card/void"
43
+
44
+ require "eml/uk/resources/agreement"
45
+ require "eml/uk/resources/card"
46
+
47
+ require "eml/uk/response"
48
+ require "eml/uk/responses/agreement/show"
49
+ require "eml/uk/responses/card/reload"
50
+ require "eml/uk/responses/card/show"
51
+ require "eml/uk/responses/card/transaction"
@@ -0,0 +1,94 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require "cgi"
5
+
6
+ module EML
7
+ module UK
8
+ class APIResource
9
+ extend T::Sig
10
+
11
+ LANGUAGE = "en"
12
+ private_constant :LANGUAGE
13
+
14
+ sig { params(id: T.nilable(String)).void }
15
+ def initialize(id: nil)
16
+ @id = T.let(id, T.nilable(String))
17
+ @headers = T.let(nil, T.nilable(T::Hash[String, String]))
18
+ end
19
+
20
+ sig do
21
+ params(
22
+ action: Symbol,
23
+ endpoint: String,
24
+ payload: T::Hash[Symbol, T.untyped],
25
+ params: T::Hash[Symbol, T.untyped]
26
+ ).returns(EML::Response)
27
+ end
28
+ def request(action, endpoint = "", payload: {}, params: {})
29
+ payload = EML::UK::Payload.convert(self.class, endpoint, payload)
30
+ params = EML::UK::Parameters.convert(self.class, endpoint, params)
31
+ url = resource_url(endpoint)
32
+
33
+ response = HTTP.headers(headers).
34
+ public_send(action, url, json: payload.to_h, params: params.to_h)
35
+ response_class(endpoint).new(response, id: @id)
36
+ end
37
+
38
+ protected
39
+
40
+ sig { params(endpoint: String).returns(String) }
41
+ def resource_url(endpoint)
42
+ endpoint_base = self.class.const_get(:ENDPOINT_BASE)
43
+
44
+ if @id.nil?
45
+ "#{domain}/#{endpoint_base}/#{endpoint}"
46
+ else
47
+ "#{domain}/#{endpoint_base}/#{@id}/#{endpoint}"
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ sig { returns(T::Hash[Symbol, T.nilable(String)]) }
54
+ def credentials
55
+ config = EML::UK.config
56
+ {
57
+ username: config.username,
58
+ password: config.password,
59
+ }
60
+ end
61
+
62
+ sig { returns(String) }
63
+ def domain
64
+ if EML::UK.config.environment.test?
65
+ "https://webservices.xtest.storefinancial.net/api/v1/#{LANGUAGE}"
66
+ else
67
+ "https://webservices.storefinancial.net/api/v1/#{LANGUAGE}"
68
+ end
69
+ end
70
+
71
+ sig { returns(T::Hash[String, String]) }
72
+ def headers
73
+ @headers ||= { "Authorization" => "Basic #{base64_credentials}" }
74
+ end
75
+
76
+ sig { returns(String) }
77
+ def base64_credentials
78
+ username = credentials[:username]
79
+ password = credentials[:password]
80
+ Base64.encode64("#{username}:#{password}").tr("\n", "")
81
+ end
82
+
83
+ sig { params(endpoint: String).returns(T.class_of(EML::Response)) }
84
+ def response_class(endpoint)
85
+ endpoint_class = EML::UK::EndpointClass.(
86
+ class_type: "Responses",
87
+ resource_class: self.class,
88
+ endpoint: endpoint
89
+ )
90
+ endpoint_class || EML::Response
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,52 @@
1
+ # typed: ignore
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ module UK
6
+ class << self
7
+ extend T::Sig
8
+
9
+ sig { void }
10
+ def configure
11
+ yield config
12
+ end
13
+
14
+ sig { returns(Config) }
15
+ def config
16
+ @config ||= T.let(Config.new, Config)
17
+ end
18
+ end
19
+
20
+ class Config < ::EML::Config
21
+ extend T::Sig
22
+
23
+ sig { returns(String) }
24
+ attr_accessor :username
25
+
26
+ sig { returns(String) }
27
+ attr_accessor :password
28
+
29
+ sig { returns(String) }
30
+ attr_accessor :merchant_group
31
+
32
+ sig { returns(String) }
33
+ attr_accessor :program
34
+
35
+ sig { returns(String) }
36
+ attr_accessor :search_parameter
37
+
38
+ private
39
+
40
+ sig { params(param: Symbol).void }
41
+ def require_error(param)
42
+ raise(
43
+ ArgumentError,
44
+ "#{param} is required but hasn't been set.\n" \
45
+ "EML::UK::Config.configuration do |config|\n" +
46
+ %( config.#{param} = "value") + "\n" \
47
+ "end"
48
+ )
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,19 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module EML
5
+ module UK
6
+ class EndpointClass < ::EML::EndpointClass
7
+ extend T::Sig
8
+
9
+ private
10
+
11
+ sig { returns(String) }
12
+ def action_class_name
13
+ return "Show" if @endpoint.empty?
14
+
15
+ super
16
+ end
17
+ end
18
+ end
19
+ end