rocketgate-ruby 0.0.1.pre

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