cybersourcery 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ca8d9ed8a23a07c08e2b1186bceea38f2f9fa844
4
+ data.tar.gz: 19cbb60e854afc51e9cb69cfc56d55db502ca1e0
5
+ SHA512:
6
+ metadata.gz: 274dd59a12076d27129d85209ac74bfe6d9ff42c9c884c7ac4be2ffb67cc11a82ddbb8911da1e31f1f22ef3a5d3ba544967f16c9c68081de4155f33b1527b2a8
7
+ data.tar.gz: 39ad67705491c6f6b49ac3ed257f5c2f0883bb14262e47cd3e2ca8d0b05988b3cac1ff8f93a00c4ec34f51fe9526b2de6cd5a480f4ebd4af80a2f9255dc85275
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 PromptWorks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run RSpec code examples - NOTE: start the proxy server in spec/demo for feature tests to pass - rake cybersourcery:proxy'
5
+ task default: [:spec]
6
+ RSpec::Core::RakeTask.new
@@ -0,0 +1,46 @@
1
+ require 'cybersourcery/version'
2
+ require 'cybersourcery/configuration'
3
+ require 'cybersourcery/exceptions'
4
+
5
+ if defined? Rails
6
+ require 'cybersourcery/merchant_data_serializer'
7
+ require 'cybersourcery/profile'
8
+ require 'cybersourcery/payment'
9
+ require 'cybersourcery/railtie'
10
+ require 'cybersourcery/cybersource_signer'
11
+ require 'cybersourcery/cart_signer'
12
+ require 'cybersourcery/signature_checker'
13
+ require 'cybersourcery/cart_signature_checker'
14
+ require 'cybersourcery/cybersource_signature_checker'
15
+ require 'cybersourcery/reason_code_checker'
16
+ require 'cybersourcery/container'
17
+ require 'cybersourcery/cybersource_params_normalizer'
18
+
19
+ if Rails.env.test?
20
+ require 'slim-rails'
21
+ require 'bootstrap-sass'
22
+ require 'simple_form'
23
+ require 'sass-rails'
24
+ require 'coffee-rails'
25
+ require 'jquery-rails'
26
+ require 'dotenv-rails'
27
+ end
28
+ end
29
+
30
+ module Cybersourcery
31
+ class << self
32
+ attr_writer :configuration
33
+ end
34
+
35
+ def self.configuration
36
+ @configuration ||= Configuration.new
37
+ end
38
+
39
+ def self.reset
40
+ @configuration = Configuration.new
41
+ end
42
+
43
+ def self.configure
44
+ yield(configuration)
45
+ end
46
+ end
@@ -0,0 +1,17 @@
1
+ module Cybersourcery
2
+ class CartSignatureChecker < Cybersourcery::SignatureChecker
3
+ attr_reader :session
4
+
5
+ def post_initialize(args)
6
+ @session = args[:session]
7
+ end
8
+
9
+ def signed_field_names
10
+ @session[:signed_cart_fields]
11
+ end
12
+
13
+ def signature
14
+ @session[:cart_signature]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ module Cybersourcery
2
+ class CartSigner
3
+ attr_reader :session, :signer, :cart_fields, :signed_cart_fields
4
+
5
+ def initialize(session, signer, cart_fields)
6
+ @session = session
7
+ @signer = signer
8
+ @cart_fields = cart_fields.dup
9
+ end
10
+
11
+ def run
12
+ sign_cart_fields
13
+ reassign_signature_and_signed_fields_to_session
14
+ @signed_cart_fields
15
+ end
16
+
17
+ private
18
+
19
+ def sign_cart_fields
20
+ @cart_fields[:signed_field_names] = @cart_fields.keys.join(',')
21
+ @signer.form_fields = @cart_fields
22
+ @signed_cart_fields = @signer.signed_fields.dup
23
+ end
24
+
25
+ def reassign_signature_and_signed_fields_to_session
26
+ @session[:signed_cart_fields] = @signed_cart_fields.delete :signed_field_names
27
+ @session[:cart_signature] = @signed_cart_fields.delete :signature
28
+ @session
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ module Cybersourcery
2
+ class Configuration
3
+ attr_accessor :profiles_path, :sop_proxy_url, :sop_live_url, :sop_test_url
4
+
5
+ def profiles
6
+ @profiles ||= YAML.load_file profiles_path
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ module Cybersourcery
2
+ class Container
3
+ def self.get_cart_signer(profile_name, session, cart_fields)
4
+ profile = Cybersourcery::Profile.new(profile_name)
5
+ cybersource_signer = Cybersourcery::CybersourceSigner.new(profile)
6
+ Cybersourcery::CartSigner.new(session, cybersource_signer, cart_fields)
7
+ end
8
+
9
+ def self.get_cart_signature_checker(profile_name, params, session)
10
+ profile = Cybersourcery::Profile.new(profile_name)
11
+ Cybersourcery::CartSignatureChecker.new({ profile: profile, params: params, session: session})
12
+ end
13
+
14
+ def self.get_cybersource_signature_checker(profile_name, params)
15
+ profile = Cybersourcery::Profile.new(profile_name)
16
+ Cybersourcery::CybersourceSignatureChecker.new({ profile: profile, params: params })
17
+ end
18
+
19
+ def self.get_payment(profile_name, params, payment_class_name = 'Payment')
20
+ profile = Cybersourcery::Profile.new(profile_name)
21
+ signer = Cybersourcery::CybersourceSigner.new(profile)
22
+ payment_class_name.constantize.new(signer, profile, params)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,9 @@
1
+ module Cybersourcery
2
+ class CybersourceParamsNormalizer
3
+ def self.run(params)
4
+ params.keys.each do |key|
5
+ params[key[4..-1]] = params[key] if key =~ /^req_/
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ module Cybersourcery
2
+ class CybersourceSignatureChecker < Cybersourcery::SignatureChecker
3
+
4
+ def signed_field_names
5
+ @params['signed_field_names']
6
+ end
7
+
8
+
9
+ def signature
10
+ @params['signature']
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,79 @@
1
+ require 'hmac-sha2'
2
+ require 'base64'
3
+
4
+ module Cybersourcery
5
+ class CybersourceSigner
6
+ attr_accessor :profile, :signer
7
+ attr_writer :time
8
+ attr_writer :form_fields
9
+ attr_reader :signable_fields
10
+
11
+ IGNORE_FIELDS = %i[
12
+ commit
13
+ utf8
14
+ authenticity_token
15
+ action
16
+ controller
17
+ ]
18
+
19
+ def initialize(profile, signer = Signer)
20
+ @profile = profile
21
+ @signer = signer
22
+ @signable_fields = {
23
+ access_key: @profile.access_key,
24
+ profile_id: @profile.profile_id,
25
+ payment_method: @profile.payment_method,
26
+ locale: @profile.locale,
27
+ transaction_type: @profile.transaction_type,
28
+ currency: @profile.currency
29
+ }
30
+ end
31
+
32
+ def add_and_sign_fields(params)
33
+ add_signable_fields(params)
34
+ signed_fields
35
+ end
36
+
37
+ def add_signable_fields(params)
38
+ @signable_fields.merge! params.symbolize_keys.delete_if { |k,v|
39
+ @profile.unsigned_field_names.include?(k) || IGNORE_FIELDS.include?(k)
40
+ }
41
+ end
42
+
43
+ def signed_fields
44
+ form_fields.tap do |data|
45
+ signature_keys = data[:signed_field_names].split(',').map(&:to_sym)
46
+ signature_message = self.class.signature_message(data, signature_keys)
47
+ data[:signature] = signer.signature(signature_message, profile.secret_key)
48
+ end
49
+ end
50
+
51
+ def form_fields
52
+ @form_fields ||= signable_fields.dup.merge(
53
+ unsigned_field_names: @profile.unsigned_field_names.join(','),
54
+ transaction_uuid: SecureRandom.hex(16),
55
+ reference_number: SecureRandom.hex(16),
56
+ signed_date_time: time,
57
+ signed_field_names: nil # make sure it's in data.keys
58
+ ).tap do |data|
59
+ data[:signed_field_names] = data.keys.join(',')
60
+ end
61
+ end
62
+
63
+ def time
64
+ @time ||= Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
65
+ end
66
+
67
+ def self.signature_message(hash, keys)
68
+ keys.map {|key| "#{key}=#{hash.fetch(key)}" }.join(',')
69
+ end
70
+
71
+ class Signer
72
+ def self.signature(message, secret_key)
73
+ mac = HMAC::SHA256.new(secret_key)
74
+ mac.update message
75
+ Base64.strict_encode64(mac.digest)
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ module Cybersourcery
2
+ class CybersourceryError < StandardError; end
3
+ end
@@ -0,0 +1,38 @@
1
+ module Cybersourcery
2
+ class MerchantDataSerializer
3
+ attr_reader :start_count
4
+
5
+ def initialize(start_count = 1)
6
+ @start_count = start_count
7
+ end
8
+
9
+ def serialize(merchant_data)
10
+ scanned_data = merchant_data.to_json.scan(/.{1,100}/)
11
+ serialized_data = {}
12
+
13
+ scanned_data.each_with_index do |item, index|
14
+ count = index + @start_count
15
+
16
+ if count < 1 || count > 100
17
+ raise Cybersourcery::CybersourceryError, "The supported merchant_defined_data range is 1 to 100. #{count} is out of range."
18
+ end
19
+
20
+ serialized_data["merchant_defined_data#{count}".to_sym] = item
21
+ end
22
+
23
+ serialized_data
24
+ end
25
+
26
+ def deserialize(params)
27
+ merchant_data = params.select { |k,v| k =~ /^merchant_defined_data/ }.symbolize_keys
28
+ merchant_data_string = ''
29
+
30
+ # it's important to reassemble the data in the right order!
31
+ merchant_data.length.times do |i|
32
+ merchant_data_string << merchant_data["merchant_defined_data#{i+1}".to_sym]
33
+ end
34
+
35
+ JSON.parse(merchant_data_string).symbolize_keys
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,43 @@
1
+ module Cybersourcery
2
+ class Payment
3
+ # These are dependencies for ActiveModel::Errors in the initialize method
4
+ extend ActiveModel::Naming
5
+ extend ActiveModel::Translation
6
+ include ActiveModel::Validations
7
+
8
+ # So we can use form_for in a view
9
+ include ActiveModel::Conversion
10
+
11
+ attr_reader :signer, :profile, :params, :errors
12
+ attr_accessor :bill_to_forename, :bill_to_surname, :card_number, :card_expiry_date,
13
+ :card_expiry_month, :card_expiry_year, :card_type,
14
+ :bill_to_email, :bill_to_address_line1, :bill_to_address_line2,
15
+ :bill_to_address_city, :bill_to_address_state, :bill_to_address_postal_code
16
+ validates_presence_of :bill_to_forename, :bill_to_surname, :card_number, :card_expiry_date,
17
+ :card_expiry_month, :card_expiry_year, :card_type, :bill_to_email,
18
+ :bill_to_address_line1, :bill_to_address_city, :bill_to_address_state,
19
+ :bill_to_address_postal_code
20
+
21
+ # To keep ActiveModel::Conversion happy
22
+ def persisted?
23
+ false
24
+ end
25
+
26
+ def initialize(signer, profile, params)
27
+ @signer = signer
28
+ @profile = profile
29
+ @params = params
30
+ # I'm not doing dependency injection for ActiveModel dependencies.
31
+ # Given we're extending ActiveModel::Naming above, we're already tightly bound...
32
+ @errors = ActiveModel::Errors.new(self)
33
+ end
34
+
35
+ def form_action_url
36
+ @profile.transaction_url
37
+ end
38
+
39
+ def signed_fields
40
+ @signer.add_and_sign_fields(@params)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,404 @@
1
+ module Cybersourcery
2
+ module PaymentsHelper
3
+ def add_signed_fields(payment, form)
4
+ signed_fields = ''
5
+
6
+ payment.signed_fields.each do |field, value|
7
+ signed_fields << hidden_input(form, field, value)
8
+ end
9
+
10
+ signed_fields
11
+ end
12
+
13
+ def simple_form_input(form, field, value = nil)
14
+ form.input field, label: field_label(field), input_html: {
15
+ name: field.to_s,
16
+ value: value,
17
+ pattern: field_pattern(field),
18
+ title: field_validation_message(field)
19
+ }
20
+ end
21
+
22
+ def simple_form_select(form, field, collection, selected = nil, prompt = nil)
23
+ form.input(
24
+ field,
25
+ label: field_label(field),
26
+ collection: collection,
27
+ selected: selected,
28
+ prompt: prompt,
29
+ input_html: { name: field.to_s }
30
+ )
31
+ end
32
+
33
+ def field_label(field)
34
+ labels = {
35
+ bill_to_forename: 'First Name',
36
+ bill_to_surname: 'Last Name',
37
+ card_number: 'Card Number',
38
+ card_cvn: 'Security Code',
39
+ card_expiry_dummy: 'Expiration',
40
+ bill_to_email: 'Email',
41
+ bill_to_address_line1: 'Street Address (line 1)',
42
+ bill_to_address_line2: 'Street Address (line 2)',
43
+ bill_to_address_city: 'City',
44
+ bill_to_address_state: 'State (Province)',
45
+ bill_to_address_postal_code: 'Zip (Postal Code)'
46
+ }
47
+ labels[field]
48
+ end
49
+
50
+ def field_pattern(field)
51
+ patterns = {
52
+ card_number: "^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\d{3})\d{11})$"
53
+ }
54
+ patterns[field]
55
+ end
56
+
57
+ def field_validation_message(field)
58
+ patterns = {
59
+ card_number: 'Please enter a valid credit card number'
60
+ }
61
+ patterns[field]
62
+ end
63
+
64
+ def hidden_input(form, field, value = nil)
65
+ form.hidden_field field, value: value, name: field.to_s
66
+ end
67
+
68
+ def add_expiry_date_fields(form, classes = 'select required form-control card-expiry')
69
+ date_fields = hidden_input form, :card_expiry_date
70
+ date_fields << form.input(:card_expiry_dummy, label: field_label(:card_expiry_dummy)) do
71
+ form.date_select(:card_expiry_dummy,
72
+ {
73
+ discard_day: true,
74
+ order: [:month, :year],
75
+ start_year: Date.today.year,
76
+ end_year: (Date.today.year + 19),
77
+ use_two_digit_numbers: true,
78
+ date_separator: ' / ',
79
+ prompt: true
80
+ },
81
+ { class: classes }
82
+ )
83
+ end
84
+ end
85
+
86
+ def countries
87
+ {
88
+ "Afghanistan" => "AF",
89
+ "Aland Islands" => "AX",
90
+ "Albania" => "AL",
91
+ "Algeria" => "DZ",
92
+ "American Samoa (US)" => "AS",
93
+ "Andorra" => "AD",
94
+ "Angola" => "AO",
95
+ "Anguilla (UK)" => "AI",
96
+ "Antarctica" => "AQ",
97
+ "Antigua and Barbuda" => "AG",
98
+ "Argentina" => "AR",
99
+ "Armenia" => "AM",
100
+ "Aruba" => "AW",
101
+ "Australia" => "AU",
102
+ "Austria" => "AT",
103
+ "Azerbaijan" => "AZ",
104
+ "Bahamas" => "BS",
105
+ "Bahrain" => "BH",
106
+ "Bangladesh" => "BD",
107
+ "Barbados" => "BB",
108
+ "Belarus" => "BY",
109
+ "Belgium" => "BE",
110
+ "Belize" => "BZ",
111
+ "Benin" => "BJ",
112
+ "Bermuda (UK)" => "BM",
113
+ "Bhutan" => "BT",
114
+ "Bolivia" => "BO",
115
+ "Bonaire, Sint Eustatius and Saba" => "BQ",
116
+ "Bosnia and Herzegovina" => "BA",
117
+ "Botswana" => "BW",
118
+ "Brazil" => "BR",
119
+ "British Indian Ocean Territory" => "IO",
120
+ "British Virgin Islands (UK)" => "VG",
121
+ "Brunei Darussalam" => "BN",
122
+ "Bulgaria" => "BG",
123
+ "Burkina Faso" => "BF",
124
+ "Burundi" => "BI",
125
+ "Cambodia" => "KH",
126
+ "Cameroon" => "CM",
127
+ "Canada" => "CA",
128
+ "Cape Verde" => "CV",
129
+ "Cayman Islands (UK)" => "KY",
130
+ "Central African Republic" => "CF",
131
+ "Chad" => "TD",
132
+ "Chile" => "CL",
133
+ "China" => "CN",
134
+ "Christmas Island (AU)" => "CX",
135
+ "Cocos (Keeling) Islands (AU)" => "CC",
136
+ "Colombia" => "CO",
137
+ "Comoros" => "KM",
138
+ "Congo, Democratic Republic of the" => "CD",
139
+ "Congo, Republic of the" => "CG",
140
+ "Cook Islands (NZ)" => "CK",
141
+ "Costa Rica" => "CR",
142
+ "Côte D'Ivoire" => "CI",
143
+ "Croatia" => "HR",
144
+ "Cuba" => "CU",
145
+ "Curaçao" => "CW",
146
+ "Cyprus" => "CY",
147
+ "Czech Republic" => "CZ",
148
+ "Denmark" => "DK",
149
+ "Djibouti" => "DJ",
150
+ "Dominica" => "DM",
151
+ "Dominican Republic" => "DO",
152
+ "Ecuador" => "EC",
153
+ "Egypt" => "EG",
154
+ "El Salvador" => "SV",
155
+ "Equatorial Guinea" => "GQ",
156
+ "Eritrea" => "ER",
157
+ "Estonia" => "EE",
158
+ "Ethiopia" => "ET",
159
+ "Falkland Islands (UK)" => "FK",
160
+ "Faroe Islands (DK)" => "FO",
161
+ "Fiji" => "FJ",
162
+ "Finland" => "FI",
163
+ "France" => "FR",
164
+ "French Guiana (FR)" => "GF",
165
+ "French Polynesia (FR)" => "PF",
166
+ "French Southern Territories" => "TF",
167
+ "Gabon" => "GA",
168
+ "Gambia" => "GM",
169
+ "Georgia" => "GE",
170
+ "Germany" => "DE",
171
+ "Ghana" => "GH",
172
+ "Gibraltar (UK)" => "GI",
173
+ "Greece" => "GR",
174
+ "Greenland (DK)" => "GL",
175
+ "Grenada" => "GD",
176
+ "Guadeloupe (FR)" => "GP",
177
+ "Guam (US)" => "GU",
178
+ "Guatemala" => "GT",
179
+ "Guernsey" => "GG",
180
+ "Guinea" => "GN",
181
+ "Guinea-Bissau" => "GW",
182
+ "Guyana" => "GY",
183
+ "Haiti" => "HT",
184
+ "Heard Island and McDonald Islands" => "HM",
185
+ "Holy See (Vatican City)" => "VA",
186
+ "Honduras" => "HN",
187
+ "Hong Kong (CN)" => "HK",
188
+ "Hungary" => "HU",
189
+ "Iceland" => "IS",
190
+ "India" => "IN",
191
+ "Indonesia" => "ID",
192
+ "Iran" => "IR",
193
+ "Iraq" => "IQ",
194
+ "Ireland" => "IE",
195
+ "Isle of Man" => "IM",
196
+ "Israel" => "IL",
197
+ "Italy" => "IT",
198
+ "Jamaica" => "JM",
199
+ "Japan" => "JP",
200
+ "Jersey" => "JE",
201
+ "Jordan" => "JO",
202
+ "Kazakhstan" => "KZ",
203
+ "Kenya" => "KE",
204
+ "Kiribati" => "KI",
205
+ "Korea, Democratic People's Republic (North)" => "KP",
206
+ "Korea, Republic of (South)" => "KR",
207
+ "Kuwait" => "KW",
208
+ "Kyrgyzstan" => "KG",
209
+ "Laos" => "LA",
210
+ "Latvia" => "LV",
211
+ "Lebanon" => "LB",
212
+ "Lesotho" => "LS",
213
+ "Liberia" => "LR",
214
+ "Libya" => "LY",
215
+ "Liechtenstein" => "LI",
216
+ "Lithuania" => "LT",
217
+ "Luxembourg" => "LU",
218
+ "Macau (CN)" => "MO",
219
+ "Macedonia" => "MK",
220
+ "Madagascar" => "MG",
221
+ "Malawi" => "MW",
222
+ "Malaysia" => "MY",
223
+ "Maldives" => "MV",
224
+ "Mali" => "ML",
225
+ "Malta" => "MT",
226
+ "Marshall Islands" => "MH",
227
+ "Martinique (FR)" => "MQ",
228
+ "Mauritania" => "MR",
229
+ "Mauritius" => "MU",
230
+ "Mayotte (FR)" => "YT",
231
+ "Mexico" => "MX",
232
+ "Micronesia, Federated States of" => "FM",
233
+ "Moldova Republic of" => "MD",
234
+ "Monaco" => "MC",
235
+ "Mongolia" => "MN",
236
+ "Montenegro" => "ME",
237
+ "Montserrat (UK)" => "MS",
238
+ "Morocco" => "MA",
239
+ "Mozambique" => "MZ",
240
+ "Myanmar" => "MM",
241
+ "Namibia" => "NA",
242
+ "Nauru" => "NR",
243
+ "Nepal" => "NP",
244
+ "Netherlands" => "NL",
245
+ "Netherlands Antilles (NL)" => "AN",
246
+ "New Caledonia (FR)" => "NC",
247
+ "New Zealand" => "NZ",
248
+ "Nicaragua" => "NI",
249
+ "Niger" => "NE",
250
+ "Nigeria" => "NG",
251
+ "Niue" => "NU",
252
+ "Norfolk Island (AU)" => "NF",
253
+ "Northern Mariana Islands (US)" => "MP",
254
+ "Norway" => "NO",
255
+ "Oman" => "OM",
256
+ "Pakistan" => "PK",
257
+ "Palau" => "PW",
258
+ "Palestinian Territories" => "PS",
259
+ "Panama" => "PA",
260
+ "Papua New Guinea" => "PG",
261
+ "Paraguay" => "PY",
262
+ "Peru" => "PE",
263
+ "Philippines" => "PH",
264
+ "Pitcairn Islands (UK)" => "PN",
265
+ "Poland" => "PL",
266
+ "Portugal" => "PT",
267
+ "Puerto Rico (US)" => "PR",
268
+ "Qatar" => "QA",
269
+ "Reunion (FR)" => "RE",
270
+ "Romania" => "RO",
271
+ "Russia" => "RU",
272
+ "Rwanda" => "RW",
273
+ "Saint Barthelemy" => "BL",
274
+ "Saint Helena (UK)" => "SH",
275
+ "Saint Kitts and Nevis" => "KN",
276
+ "Saint Lucia" => "LC",
277
+ "Saint Martin" => "MF",
278
+ "Saint Pierre & Miquelon (FR)" => "PM",
279
+ "Saint Vincent and the Grenadines" => "VC",
280
+ "Samoa" => "WS",
281
+ "San Marino" => "SM",
282
+ "Sao Tome and Principe" => "ST",
283
+ "Saudi Arabia" => "SA",
284
+ "Senegal" => "SN",
285
+ "Serbia" => "RS",
286
+ "Seychelles" => "SC",
287
+ "Sierra Leone" => "SL",
288
+ "Singapore" => "SG",
289
+ "Sint Maarten (Dutch Part)" => "SX",
290
+ "Slovakia" => "SK",
291
+ "Slovenia" => "SI",
292
+ "Solomon Islands" => "SB",
293
+ "Somalia" => "SO",
294
+ "South Africa" => "ZA",
295
+ "South Georgia & South Sandwich Islands (UK)" => "GS",
296
+ "South Sudan" => "SS",
297
+ "Spain" => "ES",
298
+ "Sri Lanka" => "LK",
299
+ "Sudan" => "SD",
300
+ "Suriname" => "SR",
301
+ "Svalbard and Jan Mayen" => "SJ",
302
+ "Swaziland" => "SZ",
303
+ "Sweden" => "SE",
304
+ "Switzerland" => "CH",
305
+ "Syria" => "SY",
306
+ "Taiwan" => "TW",
307
+ "Tajikistan" => "TJ",
308
+ "Tanzania" => "TZ",
309
+ "Thailand" => "TH",
310
+ "Timor-Leste" => "TL",
311
+ "Togo" => "TG",
312
+ "Tokelau" => "TK",
313
+ "Tonga" => "TO",
314
+ "Trinidad and Tobago" => "TT",
315
+ "Tunisia" => "TN",
316
+ "Turkey" => "TR",
317
+ "Turkmenistan" => "TM",
318
+ "Turks and Caicos Islands (UK)" => "TC",
319
+ "Tuvalu" => "TV",
320
+ "Uganda" => "UG",
321
+ "Ukraine" => "UA",
322
+ "United Arab Emirates" => "AE",
323
+ "United Kingdom" => "GB",
324
+ "United States" => "US",
325
+ "United States Minor Outlying Islands" => "UM",
326
+ "Uruguay" => "UY",
327
+ "Uzbekistan" => "UZ",
328
+ "Vanuatu" => "VU",
329
+ "Venezuela" => "VE",
330
+ "Vietnam" => "VN",
331
+ "Virgin Islands (US)" => "VI",
332
+ "Wallis and Futuna (FR)" => "WF",
333
+ "Western Sahara" => "EH",
334
+ "Yemen" => "YE",
335
+ "Zambia" => "ZM",
336
+ "Zimbabwe" => "ZW"
337
+ }
338
+ end
339
+
340
+ def us_states
341
+ {
342
+ "Alabama" => "AL",
343
+ "Alaska" => "AK",
344
+ "Arizona" => "AZ",
345
+ "Arkansas" => "AR",
346
+ "California" => "CA",
347
+ "Colorado" => "CO",
348
+ "Connecticut" => "CT",
349
+ "Delaware" => "DE",
350
+ "Florida" => "FL",
351
+ "Georgia" => "GA",
352
+ "Hawaii" => "HI",
353
+ "Idaho" => "ID",
354
+ "Illinois" => "IL",
355
+ "Indiana" => "IN",
356
+ "Iowa" => "IA",
357
+ "Kansas" => "KS",
358
+ "Kentucky" => "KY",
359
+ "Louisiana" => "LA",
360
+ "Maine" => "ME",
361
+ "Maryland" => "MD",
362
+ "Massachusetts" => "MA",
363
+ "Michigan" => "MI",
364
+ "Minnesota" => "MN",
365
+ "Mississippi" => "MS",
366
+ "Missouri" => "MO",
367
+ "Montana" => "MT",
368
+ "Nebraska" => "NE",
369
+ "Nevada" => "NV",
370
+ "New Hampshire" => "NH",
371
+ "New Jersey" => "NJ",
372
+ "New Mexico" => "NM",
373
+ "New York" => "NY",
374
+ "North Carolina" => "NC",
375
+ "North Dakota" => "ND",
376
+ "Ohio" => "OH",
377
+ "Oklahoma" => "OK",
378
+ "Oregon" => "OR",
379
+ "Pennsylvania" => "PA",
380
+ "Rhode Island" => "RI",
381
+ "South Carolina" => "SC",
382
+ "South Dakota" => "SD",
383
+ "Tennessee" => "TN",
384
+ "Texas" => "TX",
385
+ "Utah" => "UT",
386
+ "Vermont" => "VT",
387
+ "Virginia" => "VA",
388
+ "Washington" => "WA",
389
+ "West Virginia" => "WV",
390
+ "Wisconsin" => "WI",
391
+ "Wyoming" => "WY"
392
+ }
393
+ end
394
+
395
+ def card_types
396
+ {
397
+ 'Visa' => '001',
398
+ 'MasterCard' => '002',
399
+ 'American Express' => '003',
400
+ 'Discover' => '004'
401
+ }
402
+ end
403
+ end
404
+ end
@@ -0,0 +1,54 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext/array/conversions.rb' # so we can use to_sentence
3
+
4
+ module Cybersourcery
5
+ class Profile
6
+ include ActiveModel::Validations
7
+
8
+ VALID_ENDPOINTS = {
9
+ standard: '/silent/pay',
10
+ create_payment_token: '/silent/token/create',
11
+ update_payment_token: '/silent/token/update',
12
+ iframe_standard: '/silent/embedded/pay',
13
+ iframe_create_payment_token: 'silent/embedded/token/create',
14
+ iframe_update_payment_token: '/silent/embedded/token/update'
15
+ }
16
+
17
+ attr_accessor :profile_id, :name, :service, :access_key, :secret_key, :success_url,
18
+ :transaction_type, :endpoint_type, :payment_method, :locale, :currency,
19
+ :unsigned_field_names
20
+ validates_presence_of :profile_id, :name, :service, :access_key, :secret_key
21
+ validates_inclusion_of :service, in: %w(test live), allow_nil: false
22
+ validates_inclusion_of :endpoint_type, in: VALID_ENDPOINTS.keys, allow_nil: false
23
+ validates_inclusion_of :payment_method, in: %w(card echeck), allow_nil: false
24
+ validates_format_of :locale, with: /\A[a-z]{2}-[a-z]{2}\Z/, allow_nil: false
25
+ validates_format_of :currency, with: /\A[A-Z]{3}\Z/, allow_nil: false
26
+
27
+ def initialize(profile_id, profiles = Cybersourcery.configuration.profiles)
28
+ profiles[profile_id].each do |k,v|
29
+ self.send "#{k}=", v
30
+ end
31
+
32
+ @profile_id = profile_id
33
+ @endpoint_type = @endpoint_type.to_sym
34
+
35
+ unless self.valid?
36
+ raise Cybersourcery::CybersourceryError, self.errors.full_messages.to_sentence
37
+ end
38
+ end
39
+
40
+ def transaction_url(proxy_url = ENV['CYBERSOURCERY_SOP_PROXY_URL'], env = Rails.env)
41
+ if env == 'test' && proxy_url.present?
42
+ "#{proxy_url}#{VALID_ENDPOINTS[@endpoint_type]}"
43
+ elsif env == 'test' && proxy_url.blank?
44
+ "#{Cybersourcery.configuration.sop_test_url}#{VALID_ENDPOINTS[@endpoint_type]}"
45
+ elsif @service == 'test'
46
+ "#{Cybersourcery.configuration.sop_test_url}#{VALID_ENDPOINTS[@endpoint_type]}"
47
+ elsif @service == 'live'
48
+ "#{Cybersourcery.configuration.sop_live_url}#{VALID_ENDPOINTS[@endpoint_type]}"
49
+ else
50
+ raise Cybersourcery::CybersourceryError, 'Invalid conditions for determining the transaction_url'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,9 @@
1
+ require 'cybersourcery/payments_helper'
2
+
3
+ module Cybersourcery
4
+ class Railtie < Rails::Railtie
5
+ initializer 'Cybersourcery::ViewHelpers' do |app|
6
+ ActionView::Base.send :include, PaymentsHelper
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,77 @@
1
+ module Cybersourcery
2
+ class ReasonCodeChecker
3
+ REASON_CODE_EXPLANATIONS = {
4
+ '100' => 'Successful transaction.',
5
+ '101' => 'Declined: The request is missing one or more required fields.',
6
+ '102' => 'Declined: One or more fields in the request contains invalid data',
7
+ '104' => 'Declined: An identical authorization request has been submitted within the past 15 minutes',
8
+ '150' => 'Error: General system failure. Please wait a few minutes and try again.',
9
+ '151' => 'Error: The request was received but there was a server timeout. Please wait a few minutes and try again.',
10
+ '152' => 'Error: The request was received, but a service did not finish running in time. Please wait a few minutes and try again.',
11
+ '200' => 'Declined: The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the Address Verification Service (AVS) check',
12
+ '201' => 'Declined: The issuing bank has questions about the request. Please contact your credit card company.',
13
+ '202' => 'Declined: Expired card. Please verify your card information, or try a different card.',
14
+ '203' => 'Declined: General decline of the card. No other information provided by the issuing bank. Please try a different card.',
15
+ '204' => 'Declined: Insufficient funds in the account. Please try a different card.',
16
+ '205' => 'Declined: Stolen or lost card',
17
+ '207' => 'Declined: Issuing bank unavailable. Please wait a few minutes and try again.',
18
+ '208' => 'Declined: Inactive card or card not authorized for card-not-present transactions. Please try a different card.',
19
+ '209' => 'Declined: American Express Card Identification Digits (CID) did not match. Please verify your card information, or try a different card.',
20
+ '210' => 'Declined: The card has reached the credit limit. Please try a different card.',
21
+ '211' => 'Declined: Invalid card verification number. Please verify your card information, or try a different card.',
22
+ '221' => 'Declined: The customer matched an entry on the processor’s negative file.',
23
+ '230' => 'Declined: The authorization request was approved by the issuing bank but declined by CyberSource because it did not pass the card verification (CV) check',
24
+ '231' => 'Declined: Invalid account number, or the card type is not valid for the number provided. Please try a different card.',
25
+ '232' => 'Declined: The card type is not accepted by the payment processor. Please try a different card.',
26
+ '233' => 'Declined: General decline by the processor. Please try a different card.',
27
+ '234' => 'Declined: There is a problem with your CyberSource merchant configuration',
28
+ '235' => 'Declined: The requested amount exceeds the originally authorized amount.',
29
+ '236' => 'Declined: Processor failure. Please wait a few minutes and try again.',
30
+ '237' => 'Declined: The authorization has already been reversed',
31
+ '238' => 'Declined: The authorization has already been captured',
32
+ '239' => 'Declined: The requested transaction amount must match the previous transaction amount.',
33
+ '240' => 'Declined: The card type sent is invalid or does not correlate with the credit card number',
34
+ '241' => 'Declined: The request ID is invalid',
35
+ '242' => 'Declined: You requested a capture, but there is no corresponding, unused authorization record. Occurs if there was not a previously successful authorization request or if the previously successful authorization has already been used by another capture request',
36
+ '243' => 'Declined: The transaction has already been settled or reversed',
37
+ '246' => 'Declined: The capture or credit is not voidable because the capture or credit information has already been submitted to your processor. Or, you requested a void for a type of transaction that cannot be voided',
38
+ '247' => 'Declined: You requested a credit for a capture that was previously voided',
39
+ '250' => 'Error: The request was received, but there was a timeout at the payment processor. Please try again in a few minutes.',
40
+ '251' => "Declined: The Pinless Debit card's use frequency or maximum amount per use has been exceeded.",
41
+ '254' => 'Declined: Account is prohibited from processing stand-alone refunds.',
42
+ '400' => 'Declined: Fraud score exceeds threshold.',
43
+ '450' => 'Apartment number missing or not found. Please verify your address information and try again.',
44
+ '451' => 'Insufficient address information. Please verify your address information and try again.',
45
+ '452' => 'House/Box number not found on street. Please verify your address information and try again.',
46
+ '453' => 'Multiple address matches were found. Please verify your address information and try again.',
47
+ '454' => 'P.O. Box identifier not found or out of range. Please verify your address information and try again.',
48
+ '455' => 'Route service identifier not found or out of range. Please verify your address information and try again.',
49
+ '456' => 'Street name not found in Postal code. Please verify your address information and try again.',
50
+ '457' => 'Postal code not found in database. Please verify your address information and try again.',
51
+ '458' => 'Unable to verify or correct address. Please verify your address information and try again.',
52
+ '459' => 'Multiple international address matches were found. Please verify your address information and try again.',
53
+ '460' => 'Address match not found. Please verify your address information and try again.',
54
+ '461' => 'Error: Unsupported character set.',
55
+ '475' => 'Error: The cardholder is enrolled in Payer Authentication. Please authenticate the cardholder before continuing with the transaction.',
56
+ '476' => 'Error: Encountered a Payer Authentication problem. Payer could not be authenticated.',
57
+ '480' => 'The order is marked for review by the Cybersource Decision Manager',
58
+ '481' => 'Error: The order has been rejected by the Cybersource Decision Manager',
59
+ '520' => 'Declined: The authorization request was approved by the issuing bank but declined by CyberSource based on your Smart Authorization settings.'
60
+ }
61
+
62
+ def self.run(reason_code)
63
+ REASON_CODE_EXPLANATIONS.fetch(
64
+ reason_code,
65
+ "Declined: unknown reason (code #{reason_code})"
66
+ )
67
+ end
68
+
69
+ def self.run!(reason_code)
70
+ if reason_code == '100'
71
+ self.run(reason_code)
72
+ else
73
+ raise Cybersourcery::CybersourceryError, self.run(reason_code)
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,40 @@
1
+ module Cybersourcery
2
+ class SignatureChecker
3
+ attr_reader :profile, :params
4
+
5
+ def initialize(args = {})
6
+ @profile = args[:profile]
7
+ @params = args[:params]
8
+ post_initialize(args)
9
+ end
10
+
11
+ # subclasses can override
12
+ def post_initialize(args)
13
+ nil
14
+ end
15
+
16
+ def run
17
+ signature == Cybersourcery::CybersourceSigner::Signer.signature(signature_message, @profile.secret_key)
18
+ end
19
+
20
+ def run!
21
+ raise Cybersourcery::CybersourceryError, 'Detected possible data tampering. Signatures do not match.' unless run
22
+ end
23
+
24
+ private
25
+
26
+ def signed_field_names
27
+ raise NotImplementedError, "This #{self.class} cannot respond to:"
28
+ end
29
+
30
+ def signature
31
+ raise NotImplementedError, "This #{self.class} cannot respond to:"
32
+ end
33
+
34
+ def signature_message
35
+ signed_fields_keys = signed_field_names.split(',')
36
+ signed_fields = @params.select { |k, v| signed_fields_keys.include? k }
37
+ Cybersourcery::CybersourceSigner.signature_message(signed_fields, signed_fields_keys)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,3 @@
1
+ module Cybersourcery
2
+ VERSION = '0.0.6'
3
+ end
@@ -0,0 +1,19 @@
1
+ module Cybersourcery
2
+ module Generators
3
+ class ConfigGenerator < Rails::Generators::Base
4
+ desc 'Creates a configuration file and an initializer file for the Cybersourcery gem'
5
+
6
+ def self.source_root
7
+ @source_root ||= File.expand_path('../templates', __FILE__)
8
+ end
9
+
10
+ def create_config_file
11
+ template 'cybersourcery_profiles.yml', File.join('config', 'cybersourcery_profiles.yml')
12
+ end
13
+
14
+ def create_initializer_file
15
+ template 'cybersourcery.rb', File.join('config', 'initializers', 'cybersourcery.rb')
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ Cybersourcery.configure do |config|
2
+ config.profiles_path = "#{Rails.root}/config/cybersourcery_profiles.yml"
3
+ config.sop_live_url = 'https://secureacceptance.cybersource.com'
4
+ config.sop_test_url = 'https://testsecureacceptance.cybersource.com'
5
+ end
@@ -0,0 +1,14 @@
1
+ replace_this_key_with_your_cybersource_profile_id:
2
+ name: A nice description
3
+ service: test
4
+ access_key: your_access_key
5
+ secret_key: your_secret_key
6
+ success_url: http://your_optional_url_for_redirect_after_a_successful_transaction.com
7
+ transaction_type: sale,create_payment_token
8
+ endpoint_type: standard
9
+ payment_method: card
10
+ locale: en-us
11
+ currency: USD
12
+ unsigned_field_names: bill_to_email,bill_to_forename,bill_to_surname,bill_to_address_line1,bill_to_address_line2,bill_to_address_country,bill_to_address_state,bill_to_address_postal_code,bill_to_address_city,card_cvn,card_expiry_date,card_number,card_type
13
+
14
+ # you can have multiple profiles here
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cybersourcery do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,293 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cybersourcery
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.6
5
+ platform: ruby
6
+ authors:
7
+ - Michael Toppa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '3.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-hmac
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: '1.6'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.6'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '10.3'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: '10.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: capybara
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '2.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '2.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: poltergeist
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ~>
88
+ - !ruby/object:Gem::Version
89
+ version: '1.5'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ~>
95
+ - !ruby/object:Gem::Version
96
+ version: '1.5'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec-rails
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ~>
102
+ - !ruby/object:Gem::Version
103
+ version: '3.0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ~>
109
+ - !ruby/object:Gem::Version
110
+ version: '3.0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: '1.3'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ~>
123
+ - !ruby/object:Gem::Version
124
+ version: '1.3'
125
+ - !ruby/object:Gem::Dependency
126
+ name: slim-rails
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ~>
130
+ - !ruby/object:Gem::Version
131
+ version: 2.1.5
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ~>
137
+ - !ruby/object:Gem::Version
138
+ version: 2.1.5
139
+ - !ruby/object:Gem::Dependency
140
+ name: bootstrap-sass
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ~>
144
+ - !ruby/object:Gem::Version
145
+ version: 3.2.0.1
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ~>
151
+ - !ruby/object:Gem::Version
152
+ version: 3.2.0.1
153
+ - !ruby/object:Gem::Dependency
154
+ name: simple_form
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ~>
158
+ - !ruby/object:Gem::Version
159
+ version: 3.1.0.rc2
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ~>
165
+ - !ruby/object:Gem::Version
166
+ version: 3.1.0.rc2
167
+ - !ruby/object:Gem::Dependency
168
+ name: sass-rails
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ~>
172
+ - !ruby/object:Gem::Version
173
+ version: '4.0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ~>
179
+ - !ruby/object:Gem::Version
180
+ version: '4.0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: coffee-rails
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ~>
186
+ - !ruby/object:Gem::Version
187
+ version: '4.0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ~>
193
+ - !ruby/object:Gem::Version
194
+ version: '4.0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: jquery-rails
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ~>
200
+ - !ruby/object:Gem::Version
201
+ version: '3.1'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ~>
207
+ - !ruby/object:Gem::Version
208
+ version: '3.1'
209
+ - !ruby/object:Gem::Dependency
210
+ name: dotenv-rails
211
+ requirement: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ~>
214
+ - !ruby/object:Gem::Version
215
+ version: '0.11'
216
+ type: :development
217
+ prerelease: false
218
+ version_requirements: !ruby/object:Gem::Requirement
219
+ requirements:
220
+ - - ~>
221
+ - !ruby/object:Gem::Version
222
+ version: '0.11'
223
+ - !ruby/object:Gem::Dependency
224
+ name: cybersourcery_testing
225
+ requirement: !ruby/object:Gem::Requirement
226
+ requirements:
227
+ - - ~>
228
+ - !ruby/object:Gem::Version
229
+ version: 0.0.2
230
+ type: :development
231
+ prerelease: false
232
+ version_requirements: !ruby/object:Gem::Requirement
233
+ requirements:
234
+ - - ~>
235
+ - !ruby/object:Gem::Version
236
+ version: 0.0.2
237
+ description: Cybersourcery takes care of the most difficult aspects of working with
238
+ Cybersource in Rails. It makes as few assumptions as possible about your business
239
+ requirements, allowing you to use any subset of features appropriate to your needs.
240
+ email:
241
+ - public@toppa.com
242
+ executables: []
243
+ extensions: []
244
+ extra_rdoc_files: []
245
+ files:
246
+ - LICENSE
247
+ - Rakefile
248
+ - lib/cybersourcery.rb
249
+ - lib/cybersourcery/cart_signature_checker.rb
250
+ - lib/cybersourcery/cart_signer.rb
251
+ - lib/cybersourcery/configuration.rb
252
+ - lib/cybersourcery/container.rb
253
+ - lib/cybersourcery/cybersource_params_normalizer.rb
254
+ - lib/cybersourcery/cybersource_signature_checker.rb
255
+ - lib/cybersourcery/cybersource_signer.rb
256
+ - lib/cybersourcery/exceptions.rb
257
+ - lib/cybersourcery/merchant_data_serializer.rb
258
+ - lib/cybersourcery/payment.rb
259
+ - lib/cybersourcery/payments_helper.rb
260
+ - lib/cybersourcery/profile.rb
261
+ - lib/cybersourcery/railtie.rb
262
+ - lib/cybersourcery/reason_code_checker.rb
263
+ - lib/cybersourcery/signature_checker.rb
264
+ - lib/cybersourcery/version.rb
265
+ - lib/rails/generators/cybersourcery/config/config_generator.rb
266
+ - lib/rails/generators/cybersourcery/config/templates/cybersourcery.rb
267
+ - lib/rails/generators/cybersourcery/config/templates/cybersourcery_profiles.yml
268
+ - lib/tasks/cybersourcery_tasks.rake
269
+ homepage: https://github.com/promptworks/cybersourcery
270
+ licenses: []
271
+ metadata: {}
272
+ post_install_message:
273
+ rdoc_options: []
274
+ require_paths:
275
+ - lib
276
+ required_ruby_version: !ruby/object:Gem::Requirement
277
+ requirements:
278
+ - - '>='
279
+ - !ruby/object:Gem::Version
280
+ version: '0'
281
+ required_rubygems_version: !ruby/object:Gem::Requirement
282
+ requirements:
283
+ - - '>='
284
+ - !ruby/object:Gem::Version
285
+ version: '0'
286
+ requirements: []
287
+ rubyforge_project:
288
+ rubygems_version: 2.4.1
289
+ signing_key:
290
+ specification_version: 4
291
+ summary: A pain removal gem for working with Cybersource Secure Acceptance Silent
292
+ Order POST
293
+ test_files: []