genesis_ruby 0.1.0 → 0.1.2

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.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/Gemfile.lock +24 -25
  4. data/README.md +401 -2
  5. data/VERSION +1 -1
  6. data/genesis_ruby.gemspec +7 -4
  7. data/lib/genesis_ruby/api/constants/date_time_formats.rb +1 -1
  8. data/lib/genesis_ruby/api/constants/transactions/parameters/refund/bank_account_types.rb +27 -0
  9. data/lib/genesis_ruby/api/notification.rb +156 -0
  10. data/lib/genesis_ruby/api/requests/base/financials/credit_card.rb +0 -2
  11. data/lib/genesis_ruby/api/requests/base/reference.rb +29 -0
  12. data/lib/genesis_ruby/api/requests/financial/capture.rb +30 -0
  13. data/lib/genesis_ruby/api/requests/financial/cards/threeds/v2/method_continue.rb +145 -0
  14. data/lib/genesis_ruby/api/requests/financial/refund.rb +48 -0
  15. data/lib/genesis_ruby/api/requests/financial/void.rb +28 -0
  16. data/lib/genesis_ruby/api/requests/non_financial/reconcile/date_range.rb +95 -0
  17. data/lib/genesis_ruby/api/requests/non_financial/reconcile/transaction.rb +37 -0
  18. data/lib/genesis_ruby/api/requests/wpf/reconcile.rb +33 -0
  19. data/lib/genesis_ruby/api/response.rb +6 -5
  20. data/lib/genesis_ruby/builder.rb +3 -2
  21. data/lib/genesis_ruby/builders/form.rb +44 -0
  22. data/lib/genesis_ruby/dependencies.rb +8 -0
  23. data/lib/genesis_ruby/network/adapter/base_adapter.rb +10 -0
  24. data/lib/genesis_ruby/network/adapter/net_http_adapter.rb +30 -8
  25. data/lib/genesis_ruby/network/base_network.rb +16 -0
  26. data/lib/genesis_ruby/network/net_http.rb +5 -0
  27. data/lib/genesis_ruby/utils/common.rb +8 -1
  28. data/lib/genesis_ruby/utils/options/api_config.rb +15 -12
  29. data/lib/genesis_ruby/utils/options/network_adapter_config.rb +3 -4
  30. data/lib/genesis_ruby/utils/threeds/v2.rb +21 -0
  31. data/lib/genesis_ruby/version.rb +1 -1
  32. data/lib/genesis_ruby.rb +5 -2
  33. metadata +20 -7
@@ -0,0 +1,27 @@
1
+ module GenesisRuby
2
+ module Api
3
+ module Constants
4
+ module Transactions
5
+ module Parameters
6
+ module Refund
7
+ # Available Bank Account Types used in the Refund transaction request
8
+ class BankAccountTypes
9
+
10
+ extend Mixins::Constants::Common
11
+
12
+ # C: for Checking accounts
13
+ CHECKING = 'C'.freeze
14
+
15
+ # S: for Savings accounts
16
+ SAVINGS = 'S'.freeze
17
+
18
+ # I: for International accounts
19
+ INTERNATIONAL = 'I'.freeze
20
+
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,156 @@
1
+ require 'cgi'
2
+ require 'digest'
3
+
4
+ module GenesisRuby
5
+ module Api
6
+ # Gateway Notification handler
7
+ class Notification
8
+
9
+ attr_reader :unique_id, :notification, :reconciliation
10
+
11
+ # Signature algorithms
12
+ SHA1_SIGNATURE_TYPE = 'SHA1'.freeze
13
+ SHA256_SIGNATURE_TYPE = 'SHA256'.freeze
14
+ SHA512_SIGNATURE_TYPE = 'SHA512'.freeze
15
+
16
+ # Possible request/response identifier fields
17
+ API_UNIQUE_FIELD = 'unique_id'.freeze
18
+ WPF_UNIQUE_FIELD = 'wpf_unique_id'.freeze
19
+ KYC_UNIQUE_FIELD = 'reference_id'.freeze
20
+
21
+ # Class constructor
22
+ def initialize(configuration, data)
23
+ @configuration = configuration
24
+
25
+ parse_notification data
26
+ end
27
+
28
+ # Check if the given data is API notification
29
+ def api_notification?
30
+ notification.key? :unique_id
31
+ end
32
+
33
+ # Check if the given data is Web Payment Form notification
34
+ def wpf_notification?
35
+ notification.key? :wpf_unique_id
36
+ end
37
+
38
+ # Check if the given data is Know Your Customer notification
39
+ def kyc_notification?
40
+ notification.key? :reference_id
41
+ end
42
+
43
+ # Generates XML document expected from the Gateway
44
+ def generate_response
45
+ response = {
46
+ notification_echo: [[fetch_response_unique_field, unique_id]].to_h
47
+ }
48
+
49
+ builder = GenesisRuby::Builder.new Builder::XML
50
+ builder.parse_structure response
51
+
52
+ builder.document
53
+ end
54
+
55
+ # Execute Reconcile API Request
56
+ def reconcile
57
+ request_object = fetch_reconciliation_request
58
+
59
+ begin
60
+ genesis = Genesis.for config: @configuration, request: request_object do |req|
61
+ req.unique_id = unique_id
62
+ end.execute
63
+
64
+ @reconciliation = genesis.response
65
+ rescue Error
66
+ @reconciliation = nil
67
+ end
68
+ end
69
+
70
+ # Determinate if the executed reconciliation response contains transaction data
71
+ def transaction_reconciliation?
72
+ response_object = reconciliation&.response_object
73
+
74
+ return false if response_object.nil?
75
+
76
+ response_object.key?(:unique_id) && response_object.key?(:transaction_id) && response_object.key?(:status)
77
+ end
78
+
79
+ private
80
+
81
+ # Parse the given notification data
82
+ def parse_notification(data, authenticate: true)
83
+ @notification = parse_raw_data data
84
+ @unique_id = fetch_unique_id
85
+
86
+ raise ParameterError, 'Invalid Genesis Notification!' if authenticate && !authentic?
87
+ end
88
+
89
+ # Parse the given raw data
90
+ def parse_raw_data(data)
91
+ normalize_data data.to_h
92
+ rescue StandardError => e
93
+ raise ParameterError, "Given notification data doesn't respond to_h! #{e.message}"
94
+ end
95
+
96
+ # Normalize the given notification data
97
+ def normalize_data(data)
98
+ data.map { |key, value| [CGI.unescape(key.to_s).to_sym, CGI.unescape(value.strip)] }.to_h
99
+ end
100
+
101
+ # Assign the unique_id property based on the given notification data
102
+ def fetch_unique_id
103
+ return @notification[API_UNIQUE_FIELD.to_sym] if api_notification?
104
+ return @notification[WPF_UNIQUE_FIELD.to_sym] if wpf_notification?
105
+ return @notification[KYC_UNIQUE_FIELD.to_sym] if kyc_notification?
106
+
107
+ nil
108
+ end
109
+
110
+ # Validate the Notification signature
111
+ def authentic?
112
+ if unique_id.nil? || notification[:signature].nil?
113
+ raise ParameterError, 'Missing Notification attributes: unique_id or signature'
114
+ end
115
+
116
+ generated_signature = fetch_signature_generator.hexdigest "#{unique_id}#{@configuration.password}"
117
+
118
+ notification[:signature] == generated_signature
119
+ end
120
+
121
+ # Fetch the hash generator based on the hash type
122
+ def fetch_signature_generator
123
+ Digest.const_get fetch_hash_type
124
+ end
125
+
126
+ # Fetch the hash algorithm by the given signature length
127
+ def fetch_hash_type
128
+ case notification[:signature].length
129
+ when 40 then SHA1_SIGNATURE_TYPE
130
+ when 64 then SHA256_SIGNATURE_TYPE
131
+ when 128 then SHA512_SIGNATURE_TYPE
132
+ else
133
+ SHA1_SIGNATURE_TYPE
134
+ end
135
+ end
136
+
137
+ # Fetches the response field identifier name witch is expected from the Gateway
138
+ def fetch_response_unique_field
139
+ return API_UNIQUE_FIELD if api_notification?
140
+ return WPF_UNIQUE_FIELD if wpf_notification?
141
+ return KYC_UNIQUE_FIELD if kyc_notification?
142
+
143
+ raise ParameterError, 'Unknown notification type!'
144
+ end
145
+
146
+ # Fetch the Reconcile object
147
+ def fetch_reconciliation_request
148
+ return Requests::NonFinancial::Reconcile::Transaction if api_notification?
149
+ return Requests::Wpf::Reconcile if wpf_notification?
150
+
151
+ raise ParameterError, 'Unsupported notification type for Reconciliation'
152
+ end
153
+
154
+ end
155
+ end
156
+ end
@@ -1,5 +1,3 @@
1
- require 'genesis_ruby/api/requests/base/financial'
2
-
3
1
  module GenesisRuby
4
2
  module Api
5
3
  module Requests
@@ -0,0 +1,29 @@
1
+ module GenesisRuby
2
+ module Api
3
+ module Requests
4
+ module Base
5
+ # Base class used in the Reference transaction requests
6
+ class Reference < Financial
7
+
8
+ include Mixins::Requests::Financial::PaymentAttributes
9
+ include Mixins::Requests::Financial::ReferenceAttributes
10
+
11
+ protected
12
+
13
+ # Reference transaction request structure
14
+ def reference_transaction_structure
15
+ raise NotImplementedError, 'Reference transaction structure must be implemented.'
16
+ end
17
+
18
+ # Payment transaction structure sent to the Gateway
19
+ def payment_transaction_structure
20
+ {
21
+ reference_id: reference_id
22
+ }.merge payment_attributes_structure, reference_transaction_structure
23
+ end
24
+
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ module GenesisRuby
2
+ module Api
3
+ module Requests
4
+ module Financial
5
+ # Capture settles a transaction which has been authorized before
6
+ class Capture < Requests::Base::Reference
7
+
8
+ include Mixins::Requests::Financial::Business::BusinessAttributes
9
+ include Mixins::Requests::RestrictedSetter
10
+
11
+ protected
12
+
13
+ # Capture Transaction Request type
14
+ def transaction_type
15
+ Api::Constants::Transactions::CAPTURE
16
+ end
17
+
18
+ # Capture Transaction Request Structure
19
+ def reference_transaction_structure
20
+ {
21
+ reference_id: reference_id,
22
+ business_attributes: business_attributes_structure
23
+ }
24
+ end
25
+
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,145 @@
1
+ require 'genesis_ruby/utils/threeds/v2'
2
+
3
+ module GenesisRuby
4
+ module Api
5
+ module Requests
6
+ module Financial
7
+ module Cards
8
+ module Threeds
9
+ module V2
10
+ # Method Continue API request
11
+ class MethodContinue < Request
12
+
13
+ include Mixins::Requests::Financial::PaymentAttributes
14
+ include Mixins::Requests::RestrictedSetter
15
+
16
+ attr_writer :url, :transaction_unique_id, :signature
17
+
18
+ class << self
19
+
20
+ def build_from_response_object(configuration, response_object)
21
+ if response_object[:threeds_method_continue_url].nil? ||
22
+ response_object[:unique_id].nil? ||
23
+ response_object[:amount].nil? || response_object[:currency].nil? ||
24
+ response_object[:timestamp].nil? || !response_object[:timestamp].is_a?(DateTime)
25
+
26
+ raise ParameterError, 'Response object is incomplete or required attributes are missing!'
27
+ end
28
+
29
+ build_method_continue_request configuration, response_object
30
+ end
31
+
32
+ private
33
+
34
+ # Build Method Continue Request
35
+ def build_method_continue_request(configuration, response_object)
36
+ request = new configuration
37
+
38
+ request.url = response_object[:threeds_method_continue_url]
39
+ request.transaction_unique_id = response_object[:unique_id]
40
+ request.amount = response_object[:amount]
41
+ request.currency = response_object[:currency]
42
+ request.transaction_timestamp = response_object[:timestamp].strftime(
43
+ Constants::DateTimeFormats::YYYY_MM_DD_H_I_S_ZULU
44
+ )
45
+
46
+ GenesisRuby::Genesis.new configuration, request
47
+ end
48
+
49
+ end
50
+
51
+ # Override default constructor with FORM Builder Interface
52
+ def initialize(configuration, _builder_interface = nil)
53
+ super(configuration, Builder::FORM)
54
+ end
55
+
56
+ # A link between the customer's browser and the card issuer must be opened with a hidden iframe
57
+ def url
58
+ return @url = generate_endpoint_url if @url.nil?
59
+
60
+ @url
61
+ end
62
+
63
+ # Equivalent to the value of the unique_id,
64
+ # received from the response of the initial transaction request
65
+ def transaction_unique_id
66
+ return extract_unique_id_from_url @url if @transaction_unique_id.nil?
67
+
68
+ @transaction_unique_id
69
+ end
70
+
71
+ # SHA512 of а concatenated string (unique_id, amount, timestamp, merchant_api_password)
72
+ def signature
73
+ return @signature unless @signature.nil?
74
+
75
+ payment_amount = @currency.nil? ? amount : transform_amount(amount, currency)
76
+
77
+ Utils::Threeds::V2.generate_signature(
78
+ unique_id: transaction_unique_id,
79
+ amount: payment_amount,
80
+ timestamp: transaction_timestamp,
81
+ merchant_password: @configuration.password
82
+ )
83
+ end
84
+
85
+ # The timestamp from the initial transaction response
86
+ def transaction_timestamp
87
+ @transaction_timestamp&.strftime(
88
+ GenesisRuby::Api::Constants::DateTimeFormats::YYYY_MM_DD_H_I_S_ZULU
89
+ )
90
+ end
91
+
92
+ # The timestamp from the initial transaction response
93
+ def transaction_timestamp=(value)
94
+ parse_date attribute: __method__, value: value, allow_empty: true
95
+ end
96
+
97
+ protected
98
+
99
+ # Init Method Continue Request configuration
100
+ def init_configuration
101
+ init_form_configuration
102
+
103
+ @api_config.type = Request::METHOD_PUT
104
+
105
+ init_api_gateway_configuration request_path: 'threeds/threeds_method/:unique_id', include_token: false
106
+ end
107
+
108
+ # Build correct endpoint url during runtime
109
+ def process_request_parameters
110
+ @api_config.url = url
111
+
112
+ super
113
+ end
114
+
115
+ # Method Continue Request structure
116
+ def populate_structure
117
+ @tree_structure = {
118
+ unique_id: transaction_unique_id,
119
+ signature: signature
120
+ }
121
+ end
122
+
123
+ private
124
+
125
+ # Fills the Unique Id in the endpoint URL
126
+ def generate_endpoint_url
127
+ @api_config.url&.sub! ':unique_id', transaction_unique_id.to_s
128
+ end
129
+
130
+ # Extract the Unique Id
131
+ def extract_unique_id_from_url(url)
132
+ uri = URI.parse url || ''
133
+ exploded_path = uri.path&.split('/')
134
+
135
+ exploded_path.last
136
+ end
137
+
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,48 @@
1
+ require 'genesis_ruby/api/constants/transactions/parameters/refund/bank_account_types'
2
+
3
+ module GenesisRuby
4
+ module Api
5
+ module Requests
6
+ module Financial
7
+ # Refund reference transaction request
8
+ class Refund < Requests::Base::Reference
9
+
10
+ include Mixins::Requests::RestrictedSetter
11
+
12
+ attr_accessor :beneficiary_bank_code, :beneficiary_name, :beneficiary_account_number, :bank, :bank_branch,
13
+ :bank_account
14
+ attr_reader :bank_account_type
15
+
16
+ # The type of account
17
+ def bank_account_type=(value)
18
+ allowed_options attribute: __method__,
19
+ allowed: Api::Constants::Transactions::Parameters::Refund::BankAccountTypes.all,
20
+ value: value,
21
+ allow_empty: true
22
+ end
23
+
24
+ protected
25
+
26
+ # Refund Transaction Request type
27
+ def transaction_type
28
+ Api::Constants::Transactions::REFUND
29
+ end
30
+
31
+ # Refund Transaction Request Structure
32
+ def reference_transaction_structure
33
+ {
34
+ beneficiary_bank_code: beneficiary_bank_code,
35
+ beneficiary_name: beneficiary_name,
36
+ beneficiary_account_number: beneficiary_account_number,
37
+ bank: bank,
38
+ bank_branch: bank_branch,
39
+ bank_account: bank_account,
40
+ bank_account_type: bank_account_type
41
+ }
42
+ end
43
+
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,28 @@
1
+ module GenesisRuby
2
+ module Api
3
+ module Requests
4
+ module Financial
5
+ # Void or undo payment transaction request
6
+ class Void < Base::Financial
7
+
8
+ include Mixins::Requests::Financial::ReferenceAttributes
9
+
10
+ protected
11
+
12
+ # Void transaction type
13
+ def transaction_type
14
+ Api::Constants::Transactions::VOID
15
+ end
16
+
17
+ # Void transaction request structure
18
+ def payment_transaction_structure
19
+ {
20
+ reference_id: reference_id
21
+ }
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,95 @@
1
+ module GenesisRuby
2
+ module Api
3
+ module Requests
4
+ module NonFinancial
5
+ module Reconcile
6
+ # Date range based reconciliation allows you to fetch information for all payment transactions from
7
+ # a terminal within a given date range.
8
+ # The response is paginated, each request will return 100 entries max.
9
+ class DateRange < Api::Request
10
+
11
+ include Mixins::Requests::RestrictedSetter
12
+
13
+ attr_reader :page
14
+
15
+ # Start of the requested date range (time is optional)
16
+ def start_date
17
+ format = if start_date_time?
18
+ Constants::DateTimeFormats::YYYY_MM_DD_H_I_S
19
+ else
20
+ Constants::DateTimeFormats::YYYY_MM_DD_ISO_8601
21
+ end
22
+
23
+ @start_date&.strftime format
24
+ end
25
+
26
+ # Start of the requested date range (time is optional)
27
+ def start_date=(value)
28
+ self.start_date_time = Utils::Common.date_has_time? value
29
+
30
+ parse_date attribute: __method__, value: value, allow_empty: false
31
+ end
32
+
33
+ # Start of the requested date range (time is optional)
34
+ def end_date
35
+ format = if end_date_time?
36
+ Constants::DateTimeFormats::YYYY_MM_DD_H_I_S
37
+ else
38
+ Constants::DateTimeFormats::YYYY_MM_DD_ISO_8601
39
+ end
40
+
41
+ @end_date&.strftime format
42
+ end
43
+
44
+ # End of the requested date range (time is optional)
45
+ def end_date=(value)
46
+ self.end_date_time = Utils::Common.date_has_time? value
47
+
48
+ parse_date attribute: __method__, value: value, allow_empty: true
49
+ end
50
+
51
+ # The page within the paginated result, defaults to 1
52
+ def page=(value)
53
+ @page = value&.to_i
54
+ end
55
+
56
+ protected
57
+
58
+ # Set Gateway API configuration
59
+ def init_configuration
60
+ init_xml_configuration
61
+ init_api_gateway_configuration request_path: 'reconcile/by_date'
62
+ @api_config.parser_skip_root_node = false
63
+ end
64
+
65
+ # API Request structure
66
+ def populate_structure
67
+ @tree_structure = {
68
+ reconcile: {
69
+ start_date: start_date,
70
+ end_date: end_date,
71
+ page: page
72
+ }
73
+ }
74
+ end
75
+
76
+ private
77
+
78
+ attr_accessor :start_date_time, :end_date_time
79
+
80
+ # Start Date has time within the given string
81
+ def start_date_time?
82
+ self.start_date_time ||= false
83
+ end
84
+
85
+ # End Date has time within the given string
86
+ def end_date_time?
87
+ self.end_date_time ||= false
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,37 @@
1
+ module GenesisRuby
2
+ module Api
3
+ module Requests
4
+ module NonFinancial
5
+ module Reconcile
6
+ # Reconcile can be used to retrieve data about a transaction.
7
+ # This can be useful if you want to retrieve information about a transaction whose status is timeout,
8
+ # which returned an error or has changed eg. has beed chargebacked.
9
+ class Transaction < Api::Request
10
+
11
+ attr_accessor :arn, :transaction_id, :unique_id
12
+
13
+ protected
14
+
15
+ # Set Gateway API configuration
16
+ def init_configuration
17
+ init_xml_configuration
18
+ init_api_gateway_configuration request_path: 'reconcile'
19
+ end
20
+
21
+ # API Request structure
22
+ def populate_structure
23
+ @tree_structure = {
24
+ reconcile: {
25
+ arn: arn,
26
+ transaction_id: transaction_id,
27
+ unique_id: unique_id
28
+ }
29
+ }
30
+ end
31
+
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,33 @@
1
+ module GenesisRuby
2
+ module Api
3
+ module Requests
4
+ module Wpf
5
+ # Reconcile can be used to retrieve data about a payment.
6
+ # This can be useful if you want to retrieve information about a payment whose status is timeout,
7
+ # which returned an error or has changed eg. has beed chargebacked.
8
+ class Reconcile < Request
9
+
10
+ attr_accessor :unique_id
11
+
12
+ protected
13
+
14
+ # Define WPF Reconcile request configuration
15
+ def init_configuration
16
+ init_xml_configuration
17
+ @api_config.url = build_request_url subdomain: 'wpf', path: 'wpf/reconcile'
18
+ end
19
+
20
+ # WPF Reconcile request structure
21
+ def populate_structure
22
+ @tree_structure = {
23
+ wpf_reconcile: {
24
+ unique_id: unique_id
25
+ }
26
+ }
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -10,10 +10,11 @@ module GenesisRuby
10
10
  # Response - process/format an incoming Genesis response
11
11
  class Response
12
12
 
13
- def initialize(configuration)
13
+ def initialize(configuration, request_api_config = {})
14
14
  @configuration = configuration
15
15
  @object_formatter = GenesisRuby::Utils::ObjectFormatter.new
16
16
  @response_formatter = GenesisRuby::Utils::Formatters::Response::Loader.new
17
+ @request_api_config = request_api_config
17
18
  end
18
19
 
19
20
  # Default Response Object initialization
@@ -54,11 +55,11 @@ module GenesisRuby
54
55
  # Load the corresponding parser based on the Network header
55
56
  def load_parser(network)
56
57
  @parser = GenesisRuby::Parsers.new(GenesisRuby::Parser::JSON) if network.json?
58
+ @parser = GenesisRuby::Parser.new(GenesisRuby::Parser::XML) if network.xml?
57
59
 
58
- if network.xml?
59
- @parser = GenesisRuby::Parser.new(GenesisRuby::Parser::XML)
60
- @parser.skip_root_node
61
- end
60
+ raise NetworkError, network.server_message if @parser.nil? || @response_raw.empty?
61
+
62
+ @parser.skip_root_node if @request_api_config[:parser_skip_root_node]
62
63
 
63
64
  @parser
64
65
  end
@@ -1,4 +1,5 @@
1
1
  require 'genesis_ruby/builders/xml'
2
+ require 'genesis_ruby/builders/form'
2
3
  require 'genesis_ruby/errors/builder_error'
3
4
 
4
5
  module GenesisRuby
@@ -17,8 +18,8 @@ module GenesisRuby
17
18
  # Initialize the Builder Interface based on the Request requirements
18
19
  def initialize(request_interface)
19
20
  case request_interface
20
- when XML
21
- @builder_context = GenesisRuby::Builders::Xml.new
21
+ when XML then @builder_context = Builders::Xml.new
22
+ when FORM then @builder_context = Builders::Form.new
22
23
  else
23
24
  raise GenesisRuby::BuilderError, 'Invalid Builder interface!'
24
25
  end