genesis_ruby 0.1.0 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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