cybersourcery 0.0.6

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,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: []