rocketgate-ruby 0.0.1.pre

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.
@@ -0,0 +1,33 @@
1
+ module RocketGate
2
+ class Customer
3
+ include Hashable, Validatable
4
+
5
+ attr_accessor :id, :first_name, :last_name, :street_address, :city,
6
+ :postal_code, :state, :country, :email, :username, :ip_address
7
+
8
+ validatable :id, :first_name, :last_name
9
+
10
+ hashable({
11
+ merchantCustomerID: :id,
12
+ customerFirstName: :first_name,
13
+ customerLastName: :last_name,
14
+ billingAddress: :street_address,
15
+ billingCity: :city,
16
+ billingState: :state,
17
+ billingZipCode: :postal_code,
18
+ billingCountry: :country,
19
+ email: :email,
20
+ username: :username,
21
+ ipAddress: :ip_address
22
+ })
23
+
24
+ def initialize(*args)
25
+ @first_name, @last_name, @street_address, @city, @state, @postal_code,
26
+ @country, @email, @username, @ip_address, @id = *args
27
+ end
28
+
29
+ def id
30
+ @id ||= SecureRandom.uuid
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,63 @@
1
+ module RocketGate::Hashable
2
+ module ClassMethods
3
+ def hashable(mapping_hash, *mergable_items)
4
+ @mappings, @mergables = {}, []
5
+
6
+ mapping_hash.each do |display_name, obj|
7
+ if self.new.send(:is_callable?, obj)
8
+ @mappings[display_name] = obj
9
+ else
10
+ raise RocketGate::ValidationError.new("Invalid attribute: #{obj}")
11
+ end
12
+ end
13
+
14
+ mergable_items.each do |obj|
15
+ if self.new.send(:is_callable?, obj)
16
+ @mergables << obj
17
+ else
18
+ raise RocketGate::ValidationError.new("Invalid attribute: #{obj}")
19
+ end
20
+ end
21
+ end
22
+
23
+ def mappings
24
+ @mappings ||= {}
25
+ end
26
+
27
+ def mergables
28
+ @mergables ||= []
29
+ end
30
+ end
31
+
32
+ def self.included(base)
33
+ base.extend(ClassMethods)
34
+ end
35
+
36
+ def to_hash
37
+ hash = Hash.new.tap do |hsh|
38
+ self.class.mappings.each do |display_name, obj|
39
+ hsh[display_name] = attribute_or_proc_value(obj)
40
+ end
41
+ end
42
+
43
+ self.class.mergables.each do |obj|
44
+ hash.merge!(attribute_or_proc_value(obj))
45
+ end if self.class.mergables.any?
46
+
47
+ hash
48
+ end
49
+
50
+ private
51
+
52
+ def attribute_or_proc_value(obj)
53
+ if obj.is_a?(Proc)
54
+ obj.call
55
+ elsif self.respond_to?(obj)
56
+ self.send(obj)
57
+ end
58
+ end
59
+
60
+ def is_callable?(obj)
61
+ obj.is_a?(Proc) || self.respond_to?(obj)
62
+ end
63
+ end
@@ -0,0 +1,48 @@
1
+ require 'gyoku'
2
+
3
+ module RocketGate
4
+ class Request
5
+ attr_accessor :transaction
6
+
7
+ def initialize(transaction = nil)
8
+ @transaction = transaction
9
+ end
10
+
11
+ def build_gateway_request
12
+ transaction.validate!
13
+
14
+ parameters = base_parameters
15
+ parameters.merge!(transaction.to_hash)
16
+
17
+ xml_head = '<?xml version="1.0" encoding="UTF-8"?>'
18
+ xml_body = Gyoku.xml({
19
+ gatewayRequest: parameters.select{|_,v| !v.nil? }
20
+ })
21
+
22
+ [xml_head, xml_body].join
23
+ end
24
+
25
+ def base_parameters
26
+ config = RocketGate.configuration
27
+ config.validate!
28
+
29
+ params = {
30
+ version: config.request_version,
31
+ merchantID: config.merchant_id,
32
+ merchantPassword: config.merchant_password
33
+ }
34
+
35
+ params.merge!({
36
+ avsCheck: yes_or_ignore(config.require_avs),
37
+ cvv2Check: yes_or_ignore(config.require_cvv),
38
+ scrub: yes_or_ignore(config.require_scrub)
39
+ }) unless transaction && transaction.is_referenced?
40
+
41
+ params
42
+ end
43
+
44
+ def yes_or_ignore(boolean)
45
+ boolean ? 'YES' : 'IGNORE'
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,64 @@
1
+ require 'nokogiri'
2
+
3
+ module RocketGate
4
+ class Response
5
+ attr_accessor :authorization, :response_code, :xml_document
6
+
7
+ def self.from_xml(xml)
8
+ response = self.new.tap do |r|
9
+ r.xml_document = Nokogiri.parse(xml)
10
+ r.build!
11
+ end
12
+ end
13
+
14
+ def build!
15
+ @response_code = RocketGate::ResponseCode::RESPONSE[find_value('responseCode')]
16
+
17
+ @authorization = RocketGate::Authorization.new.tap do |auth|
18
+ auth.avs_response = RocketGate::ResponseCode::AVS[find_value('avsResponse')]
19
+ auth.cvv_response = RocketGate::ResponseCode::CVV[find_value('cvv2Code')]
20
+ auth.reason_code = RocketGate::ResponseCode::REASON[find_value('reasonCode')]
21
+ auth.auth_code = find_value('authNo')
22
+ auth.reference_id = find_value('guidNo')
23
+ auth.approved_amount = find_value('approvedAmount').to_f
24
+ auth.approved_currency = find_value('approvedCurrency')
25
+ auth.card_hash = find_value('cardHash')
26
+ auth.card_expiration = find_value('cardExpiration')
27
+ auth.card_last_four = find_value('cardLastFour')
28
+ end
29
+ end
30
+
31
+ def confirm!
32
+ if success?
33
+ confirmation = RocketGate::Transaction.new.tap do |tx|
34
+ tx.reference_id = self.authorization.reference_id
35
+ tx.type = RocketGate::Transaction::TYPE[:confirmation]
36
+ end
37
+
38
+ RocketGate.send_request!(RocketGate::Request.new(confirmation))
39
+ else
40
+ raise RocketGate::AuthorizationError.new('Unable to confirm unauthorized transaction')
41
+ end
42
+ end
43
+
44
+ def authorized?
45
+ !!(authorization && authorization.success?)
46
+ end
47
+
48
+ def success?
49
+ authorized? && response_code == :success
50
+ end
51
+
52
+ private
53
+
54
+ def find_value(key)
55
+ nodes = xml_document.at('gatewayResponse')
56
+ if nodes && nodes.children
57
+ element = nodes % key
58
+ element.content if element
59
+ else
60
+ raise RocketGate::GatewayResponseError.new('Unexpected response format')
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,148 @@
1
+ module RocketGate::ResponseCode
2
+ AVS = {
3
+ 'D' => :match,
4
+ 'F' => :match,
5
+ 'M' => :match,
6
+ 'X' => :match,
7
+ 'Y' => :match,
8
+ 'A' => :address_only_match,
9
+ 'B' => :address_only_match,
10
+ 'P' => :zip_only_match,
11
+ 'T' => :zip_only_match,
12
+ 'W' => :zip_only_match,
13
+ 'Z' => :zip_only_match,
14
+ 'C' => :not_verified,
15
+ 'G' => :not_verified,
16
+ 'I' => :not_verified,
17
+ 'R' => :unavailable,
18
+ 'U' => :unavailable,
19
+ 'S' => :unsupported,
20
+ 'N' => :no_match
21
+ }
22
+ AVS_SUCCESSES = [ :match, :address_only_match, :zip_only_match, nil ]
23
+
24
+ CVV = {
25
+ 'M' => :match,
26
+ 'N' => :no_match,
27
+ 'P' => :not_processed,
28
+ 'S' => :should_have_been_present,
29
+ 'U' => :issuer_could_not_process
30
+ }
31
+ CVV_SUCCESSES = [ :match, :not_processed, :issuer_could_not_process, nil ]
32
+
33
+ CARD_BRAND = {
34
+ 'AMEX' => :amex,
35
+ 'CARTEBLANCHE' => :carte_blanche,
36
+ 'DINERSCLUB' => :diners_club,
37
+ 'DISCOVER' => :discover,
38
+ 'JCB' => :jcb,
39
+ 'MAESTRO' => :maestro,
40
+ 'MC' => :master_card,
41
+ 'SOLO' => :solo,
42
+ 'VISA' => :visa
43
+ }
44
+
45
+ CARD_REGION = {
46
+ '1' => :usa,
47
+ '2' => :canada,
48
+ '3' => :europe,
49
+ '4' => :asia_pacific,
50
+ '5' => :latin_america,
51
+ '6' => :africa_middle_east
52
+ }
53
+
54
+ CARD_TYPE = {
55
+ '0' => :debit,
56
+ '1' => :credit,
57
+ '2' => :both
58
+ }
59
+
60
+ REASON = {
61
+ '0' => :success,
62
+ '100' => :no_matching_transaction,
63
+ '101' => :cannot_void,
64
+ '102' => :cannot_credit,
65
+ '103' => :cannot_ticket,
66
+ '104' => :declined_generic,
67
+ '105' => :declined_over_limit,
68
+ '106' => :declined_cvv_mismatch,
69
+ '107' => :declined_expired,
70
+ '108' => :declined_call_issuer,
71
+ '109' => :declined_pickup_card,
72
+ '110' => :declined_excessive_use,
73
+ '111' => :declined_invalid_card,
74
+ '112' => :declined_invalid_expiration,
75
+ '113' => :declined_bank_unavailable,
76
+ '117' => :declined_avs_mismatch,
77
+ '123' => :declined_by_user_request,
78
+ '150' => :declined_avs_mismatch,
79
+ '151' => :declined_cvv_mismatch,
80
+ '152' => :declined_invalid_amount,
81
+ '154' => :declined_integration_error,
82
+ '156' => :declined_unsupported_card,
83
+ '157' => :declined_processor_risk,
84
+ '200' => :declined_fraud_scrubbing,
85
+ '201' => :declined_customer_blocked,
86
+ '208' => :declined_duplicate_membership_id,
87
+ '209' => :declined_duplicate_membership_card,
88
+ '210' => :declined_duplicate_membership_email,
89
+ '211' => :declined_over_max_purchase,
90
+ '212' => :declined_duplicate_transaction,
91
+ '213' => :declined_customer_velocity,
92
+ '214' => :declined_card_velocity,
93
+ '215' => :declined_email_velocity,
94
+ '216' => :declined_iovation_generic,
95
+ '217' => :declined_iovation_email_velocity,
96
+ '218' => :declined_iovation_duplicate_membership,
97
+ '219' => :declined_different_source_ip,
98
+ '220' => :declined_too_many_cards,
99
+ '221' => :declined_affiliate_blocked,
100
+ '222' => :declined_trial_abuse,
101
+ '300' => :error_dns_failed,
102
+ '301' => :error_connection_failed,
103
+ '303' => :error_read_timeout,
104
+ '311' => :error_bank_communcations,
105
+ '312' => :error_bank_communcations,
106
+ '313' => :error_bank_communcations,
107
+ '314' => :error_bank_communcations,
108
+ '315' => :error_bank_communcations,
109
+ '316' => :error_bank_communcations,
110
+ '321' => :error_bank_communcations,
111
+ '323' => :error_bank_communcations,
112
+ '325' => :error_3d_secure,
113
+ '403' => :error_invalid_card_number,
114
+ '404' => :error_invalid_expiration,
115
+ '405' => :error_invalid_amount,
116
+ '406' => :error_invalid_merchant_id,
117
+ '407' => :error_invalid_merchant_account,
118
+ '408' => :error_incompatible_card_type,
119
+ '409' => :error_no_suitable_account,
120
+ '410' => :error_invalid_transaction_id,
121
+ '411' => :error_invalid_access_code,
122
+ '412' => :error_invalid_customer_data,
123
+ '413' => :error_invalid_external_data,
124
+ '414' => :error_invalid_customer_id,
125
+ '418' => :error_invalid_currency,
126
+ '419' => :error_incompatible_currency,
127
+ '420' => :error_invalid_rebill_arguments,
128
+ '436' => :error_incompatible_descriptor,
129
+ '438' => :error_invalid_site_id,
130
+ '441' => :error_invoice_not_found,
131
+ '443' => :error_missing_customer_id,
132
+ '444' => :error_missing_customer_name,
133
+ '445' => :error_missing_customer_address,
134
+ '446' => :error_missing_cvv,
135
+ '448' => :error_no_active_membership,
136
+ '449' => :error_invalid_cvv,
137
+ '451' => :error_invalid_clone_data,
138
+ '452' => :error_redundant_operation
139
+ }
140
+
141
+ RESPONSE = {
142
+ '0' => :success,
143
+ '1' => :bank_decline,
144
+ '2' => :rocketgate_scrubbing_decline,
145
+ '3' => :system_error,
146
+ '4' => :missing_or_invalid_fields
147
+ }
148
+ end
@@ -0,0 +1,75 @@
1
+ module RocketGate
2
+ class Transaction
3
+ extend Forwardable
4
+ include Hashable, Validatable
5
+
6
+ attr_accessor :amount, :credit_card, :customer, :currency, :type, :reference_id, :id
7
+
8
+ validatable :amount, :credit_card, :customer, :currency, :id
9
+
10
+ def_delegator :credit_card, :to_hash, :credit_card_hash
11
+ def_delegator :customer, :to_hash, :customer_hash
12
+
13
+ hashable({
14
+ amount: :amount,
15
+ currency: :currency,
16
+ merchantInvoiceID: :id,
17
+ transactionType: :type,
18
+ referenceGUID: :reference_id
19
+ }, :credit_card_hash, :customer_hash)
20
+
21
+ TYPE = {
22
+ auth: 'CC_AUTH',
23
+ confirmation: 'CC_CONFIRM',
24
+ credit: 'CC_CREDIT',
25
+ purchase: 'CC_PURCHASE',
26
+ rebill_cancel: 'REBILL_CANCEL',
27
+ rebill_update: 'REBILL_UPDATE',
28
+ scrub: 'CARDSCRUB',
29
+ ticket: 'CC_TICKET',
30
+ void: 'CC_VOID'
31
+ }
32
+
33
+ def initialize(*args)
34
+ @amount, @customer, @credit_card, @currency, @id = *args
35
+ end
36
+
37
+ def id
38
+ @id ||= SecureRandom.uuid
39
+ end
40
+
41
+ def currency
42
+ @currency ||= RocketGate.configuration.currency
43
+ end
44
+
45
+ def to_hash
46
+ if is_referenced?
47
+ { referenceGUID: reference_id, transactionType: type }
48
+ else
49
+ super
50
+ end
51
+ end
52
+
53
+ def type=(new_type)
54
+ new_type = TYPE[new_type] if TYPE.keys.include?(new_type)
55
+
56
+ if TYPE.values.include?(new_type)
57
+ @type = new_type
58
+ else
59
+ raise ValidationError.new("Unknown transaction type: #{new_type}")
60
+ end
61
+ end
62
+
63
+ def type
64
+ @type ||= TYPE[:auth]
65
+ end
66
+
67
+ def is_referenced?
68
+ [ TYPE[:confirmation], TYPE[:ticket], TYPE[:void] ].include?(type) && !reference_id.nil?
69
+ end
70
+
71
+ def valid?
72
+ is_referenced? || super
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,52 @@
1
+ module RocketGate::Validatable
2
+ module ClassMethods
3
+ def validatable(*args)
4
+ args.each do |attribute|
5
+ if self.new.respond_to?(attribute)
6
+ validatable_attributes << attribute
7
+ else
8
+ raise RocketGate::ValidationError.new("Invalid attribute: #{attribute}")
9
+ end
10
+ end
11
+ end
12
+
13
+ def validatable_attributes
14
+ @validatable_attributes ||= []
15
+ end
16
+ end
17
+
18
+ def self.included(base)
19
+ base.extend(ClassMethods)
20
+ end
21
+
22
+ def valid?
23
+ self.class.validatable_attributes.all? {|v| is_object_valid?(send(v)) }
24
+ end
25
+
26
+ def validate!
27
+ if valid?
28
+ self
29
+ else
30
+ message = "Invalid attributes: #{invalid_attributes.join(', ')}"
31
+ raise RocketGate::ValidationError.new(message) unless valid?
32
+ end
33
+ end
34
+
35
+ def invalid_attributes
36
+ self.class.validatable_attributes.select {|v| !is_object_valid?(send(v)) }
37
+ end
38
+
39
+ private
40
+
41
+ def is_class_validatable?(klazz)
42
+ klazz.included_modules.include?(RocketGate::Validatable)
43
+ end
44
+
45
+ def is_object_valid?(obj)
46
+ if is_class_validatable?(obj.class)
47
+ obj.valid?
48
+ else
49
+ !obj.nil?
50
+ end
51
+ end
52
+ end