solidus_gateway 0.9.0

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.
Files changed (93) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +23 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.md +26 -0
  7. data/README.md +38 -0
  8. data/Rakefile +21 -0
  9. data/app/models/spree/billing_integration/skrill/quick_checkout.rb +48 -0
  10. data/app/models/spree/gateway/authorize_net.rb +31 -0
  11. data/app/models/spree/gateway/authorize_net_cim.rb +201 -0
  12. data/app/models/spree/gateway/balanced_gateway.rb +65 -0
  13. data/app/models/spree/gateway/banwire.rb +15 -0
  14. data/app/models/spree/gateway/beanstream.rb +193 -0
  15. data/app/models/spree/gateway/braintree_gateway.rb +184 -0
  16. data/app/models/spree/gateway/card_save.rb +10 -0
  17. data/app/models/spree/gateway/data_cash.rb +10 -0
  18. data/app/models/spree/gateway/eway.rb +20 -0
  19. data/app/models/spree/gateway/linkpoint.rb +28 -0
  20. data/app/models/spree/gateway/maxipago.rb +14 -0
  21. data/app/models/spree/gateway/migs.rb +11 -0
  22. data/app/models/spree/gateway/moneris.rb +10 -0
  23. data/app/models/spree/gateway/pay_junction.rb +16 -0
  24. data/app/models/spree/gateway/pay_pal_gateway.rb +12 -0
  25. data/app/models/spree/gateway/payflow_pro.rb +18 -0
  26. data/app/models/spree/gateway/paymill.rb +12 -0
  27. data/app/models/spree/gateway/pin_gateway.rb +60 -0
  28. data/app/models/spree/gateway/sage_pay.rb +11 -0
  29. data/app/models/spree/gateway/secure_pay_au.rb +10 -0
  30. data/app/models/spree/gateway/spreedly_core_gateway.rb +10 -0
  31. data/app/models/spree/gateway/stripe_gateway.rb +126 -0
  32. data/app/models/spree/gateway/usa_epay.rb +9 -0
  33. data/app/models/spree/gateway/worldpay.rb +91 -0
  34. data/app/models/spree/skrill_transaction.rb +19 -0
  35. data/bin/rails +7 -0
  36. data/circle.yml +6 -0
  37. data/config/locales/bg.yml +11 -0
  38. data/config/locales/de.yml +11 -0
  39. data/config/locales/en.yml +30 -0
  40. data/config/locales/sv.yml +11 -0
  41. data/config/routes.rb +12 -0
  42. data/db/migrate/20111118164631_create_skrill_transactions.rb +14 -0
  43. data/db/migrate/20121017004102_update_braintree_payment_method_type.rb +9 -0
  44. data/db/migrate/20130213222555_update_stripe_payment_method_type.rb +9 -0
  45. data/db/migrate/20130415222802_update_balanced_payment_method_type.rb +9 -0
  46. data/db/migrate/20131008221012_update_paypal_payment_method_type.rb +9 -0
  47. data/db/migrate/20131112133401_migrate_stripe_preferences.rb +8 -0
  48. data/lib/active_merchant/billing/skrill.rb +18 -0
  49. data/lib/assets/javascripts/spree/frontend/solidus_gateway.js +1 -0
  50. data/lib/assets/stylesheets/spree/frontend/solidus_gateway.css +3 -0
  51. data/lib/controllers/frontend/spree/checkout_controller_decorator.rb +51 -0
  52. data/lib/controllers/frontend/spree/skrill_status_controller.rb +39 -0
  53. data/lib/generators/solidus_gateway/install/install_generator.rb +28 -0
  54. data/lib/solidus_gateway.rb +4 -0
  55. data/lib/spree_gateway/engine.rb +67 -0
  56. data/lib/views/backend/spree/admin/log_entries/_braintree.html.erb +31 -0
  57. data/lib/views/backend/spree/admin/log_entries/_stripe.html.erb +28 -0
  58. data/lib/views/backend/spree/admin/payments/source_forms/_quickcheckout.html.erb +8 -0
  59. data/lib/views/backend/spree/admin/payments/source_forms/_stripe.html.erb +1 -0
  60. data/lib/views/backend/spree/admin/payments/source_views/_quickcheckout.html.erb +39 -0
  61. data/lib/views/backend/spree/admin/payments/source_views/_stripe.html.erb +1 -0
  62. data/lib/views/frontend/spree/checkout/payment/_quickcheckout.html.erb +26 -0
  63. data/lib/views/frontend/spree/checkout/payment/_stripe.html.erb +80 -0
  64. data/solidus_gateway.gemspec +33 -0
  65. data/spec/factories/skrill_factory.rb +6 -0
  66. data/spec/features/stripe_checkout_spec.rb +78 -0
  67. data/spec/lib/active_merchant/billing_skrill_spec.rb +18 -0
  68. data/spec/models/billing_integration/skrill_quick_checkout_spec.rb +11 -0
  69. data/spec/models/gateway/authorize_net_cim_spec.rb +29 -0
  70. data/spec/models/gateway/authorize_net_spec.rb +23 -0
  71. data/spec/models/gateway/balanced_gateway_spec.rb +17 -0
  72. data/spec/models/gateway/banwire_spec.rb +11 -0
  73. data/spec/models/gateway/beanstream_spec.rb +17 -0
  74. data/spec/models/gateway/braintree_gateway_spec.rb +437 -0
  75. data/spec/models/gateway/card_save_spec.rb +11 -0
  76. data/spec/models/gateway/data_cache_spec.rb +11 -0
  77. data/spec/models/gateway/eway_spec.rb +29 -0
  78. data/spec/models/gateway/linkpoint_spec.rb +62 -0
  79. data/spec/models/gateway/maxipago_spec.rb +17 -0
  80. data/spec/models/gateway/moneris_spec.rb +11 -0
  81. data/spec/models/gateway/pay_junction_spec.rb +23 -0
  82. data/spec/models/gateway/pay_pal_spec.rb +11 -0
  83. data/spec/models/gateway/payflow_pro_spec.rb +23 -0
  84. data/spec/models/gateway/paymill_spec.rb +11 -0
  85. data/spec/models/gateway/pin_gateway_spec.rb +55 -0
  86. data/spec/models/gateway/sage_pay_spec.rb +11 -0
  87. data/spec/models/gateway/secure_pay_au_spec.rb +11 -0
  88. data/spec/models/gateway/stripe_gateway_spec.rb +200 -0
  89. data/spec/models/gateway/usa_epay_spec.rb +49 -0
  90. data/spec/models/gateway/worldpay_spec.rb +11 -0
  91. data/spec/models/skrill_transaction_spec.rb +9 -0
  92. data/spec/spec_helper.rb +50 -0
  93. metadata +317 -0
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ class Gateway::Banwire < Gateway
3
+ preference :login, :string
4
+
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::BanwireGateway
8
+ end
9
+
10
+ def purchase(money, creditcard, gateway_options)
11
+ gateway_options[:description] = "Spree Order"
12
+ provider.purchase(money, creditcard, gateway_options)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,193 @@
1
+ module Spree
2
+ class Gateway::Beanstream < Gateway
3
+ preference :login, :string
4
+ preference :user, :string
5
+ preference :password, :string
6
+ preference :secure_profile_api_key, :string
7
+
8
+ def provider_class
9
+ ActiveMerchant::Billing::BeanstreamGateway
10
+ end
11
+
12
+ def payment_profiles_supported?
13
+ true
14
+ end
15
+
16
+ def create_profile(payment)
17
+ creditcard = payment.source
18
+ if creditcard.gateway_customer_profile_id.nil?
19
+ options = options_for_create_customer_profile(creditcard, {})
20
+ verify_creditcard_name!(creditcard)
21
+ result = provider.store(creditcard, options)
22
+ if result.success?
23
+ creditcard.update_attributes(:gateway_customer_profile_id => result.params['customerCode'], :gateway_payment_profile_id => result.params['customer_vault_id'])
24
+ else
25
+ payment.send(:gateway_error, result)
26
+ end
27
+ end
28
+ end
29
+
30
+ def capture(transaction, creditcard, gateway_options)
31
+ beanstream_gateway.capture((transaction.amount*100).round, transaction.response_code, gateway_options)
32
+ end
33
+
34
+ def void(transaction_response, creditcard, gateway_options)
35
+ beanstream_gateway.void(transaction_response, gateway_options)
36
+ end
37
+
38
+ def credit(amount, creditcard, response_code, gateway_options = {})
39
+ amount = (amount * -1) if amount < 0
40
+ beanstream_gateway.credit(amount, response_code, gateway_options)
41
+ end
42
+
43
+ private
44
+ def beanstream_gateway
45
+ ActiveMerchant::Billing::Base.gateway_mode = preferred_server.to_sym
46
+ gateway_options = options
47
+ ActiveMerchant::Billing::BeanstreamGateway.new(gateway_options)
48
+ end
49
+
50
+ def verify_creditcard_name!(creditcard)
51
+ bill_address = creditcard.payments.first.order.bill_address
52
+ creditcard.first_name = bill_address.firstname unless creditcard.first_name?
53
+ creditcard.last_name = bill_address.lastname unless creditcard.last_name?
54
+ end
55
+
56
+ def options_for_create_customer_profile(creditcard, gateway_options)
57
+ order = creditcard.payments.first.order
58
+ address = order.bill_address
59
+ { :email=>order.email,
60
+ :billing_address=>
61
+ { :name=>address.full_name,
62
+ :phone=>address.phone,
63
+ :address1=>address.address1,
64
+ :address2=>address.address2,
65
+ :city=>address.city,
66
+ :state=>address.state_name || address.state.abbr,
67
+ :country=>address.country.iso,
68
+ :zip=>address.zipcode
69
+ }
70
+ }.merge(gateway_options)
71
+ end
72
+
73
+ SECURE_PROFILE_URL = 'https://www.beanstream.com/scripts/payment_profile.asp'
74
+ SP_SERVICE_VERSION = '1.1'
75
+ PROFILE_OPERATIONS = { :new => 'N', :modify => 'M' }
76
+
77
+ ActiveMerchant::Billing::BeanstreamGateway.class_eval do
78
+
79
+ def store(credit_card, options = {})
80
+ post = {}
81
+ add_address(post, options)
82
+ add_credit_card(post, credit_card)
83
+ add_secure_profile_variables(post,options)
84
+ commit(post, true)
85
+ end
86
+
87
+ #can't actually delete a secure profile with the supplicaed API. This function sets the status of the profile to closed (C).
88
+ #Closed profiles will have to removed manually.
89
+ def delete(vault_id)
90
+ update(vault_id, false, { :status => 'C' })
91
+ end
92
+
93
+ #alias_method :unstore, :delete
94
+
95
+ # Update the values (such as CC expiration) stored at
96
+ # the gateway. The CC number must be supplied in the
97
+ # CreditCard object.
98
+ def update(vault_id, credit_card, options = {})
99
+ post = {}
100
+ add_address(post, options)
101
+ add_credit_card(post, credit_card)
102
+ options.merge!({ :vault_id => vault_id, :operation => secure_profile_action(:modify) })
103
+ add_secure_profile_variables(post,options)
104
+ commit(post, true)
105
+ end
106
+
107
+ # CORE #
108
+
109
+ def secure_profile_action(type)
110
+ PROFILE_OPERATIONS[type] || PROFILE_OPERATIONS[:new]
111
+ end
112
+
113
+ def add_credit_card(post, credit_card)
114
+ if credit_card
115
+ if credit_card.has_payment_profile?
116
+ post[:customerCode] = credit_card.gateway_customer_profile_id
117
+ else
118
+ post[:trnCardOwner] = credit_card.name
119
+ post[:trnCardNumber] = credit_card.number
120
+ post[:trnExpMonth] = format(credit_card.month, :two_digits)
121
+ post[:trnExpYear] = format(credit_card.year, :two_digits)
122
+ post[:trnCardCvd] = credit_card.verification_value
123
+ end
124
+ end
125
+ end
126
+
127
+ def add_secure_profile_variables(post, options = {})
128
+ post[:serviceVersion] = SP_SERVICE_VERSION
129
+ post[:responseFormat] = 'QS'
130
+ post[:cardValidation] = (options[:cardValidation].to_i == 1) || '0'
131
+
132
+ post[:operationType] = options[:operationType] || options[:operation] || secure_profile_action(:new)
133
+ post[:customerCode] = options[:billing_id] || options[:vault_id] || false
134
+ post[:status] = options[:status]
135
+ end
136
+
137
+ def commit(params, use_profile_api = false)
138
+ post(post_data(params,use_profile_api),use_profile_api)
139
+ end
140
+
141
+ def post(data, use_profile_api)
142
+ response = parse(ssl_post((use_profile_api ? SECURE_PROFILE_URL : ActiveMerchant::Billing::BeanstreamGateway::URL), data))
143
+ if response[:responseCode].eql?("17") # Found matching card
144
+ response[:responseCode] = "1"
145
+ response[:customerCode] = response[:matchedCustomerCode]
146
+ end
147
+ response[:customer_vault_id] = response[:customerCode] if response[:customerCode]
148
+ build_response(success?(response), message_from(response), response,
149
+ :test => test? || response[:authCode] == 'TEST',
150
+ :authorization => authorization_from(response),
151
+ :cvv_result => ActiveMerchant::Billing::BeanstreamGateway::CVD_CODES[response[:cvdId]],
152
+ :avs_result => { :code => (ActiveMerchant::Billing::BeanstreamGateway::AVS_CODES.include? response[:avsId]) ? ActiveMerchant::Billing::BeanstreamGateway::AVS_CODES[response[:avsId]] : response[:avsId] }
153
+ )
154
+ end
155
+
156
+ def message_from(response)
157
+ response[:messageText] || response[:responseMessage]
158
+ end
159
+
160
+ def success?(response)
161
+ response[:responseType] == 'R' || response[:trnApproved] == '1' || response[:responseCode] == '1'
162
+ end
163
+
164
+ def add_source(post, source)
165
+ if source.is_a?(String) or source.is_a?(Integer)
166
+ post[:customerCode] = source
167
+ else
168
+ if source.respond_to?(:type) && source.type.to_s == 'check'
169
+ add_check(post, source)
170
+ else
171
+ add_credit_card(post, source)
172
+ end
173
+ end
174
+ end
175
+
176
+ def post_data(params, use_profile_api)
177
+ params[:requestType] = 'BACKEND'
178
+ if use_profile_api
179
+ params[:merchantId] = @options[:login]
180
+ params[:passCode] = @options[:secure_profile_api_key]
181
+ else
182
+ params[:username] = @options[:user] if @options[:user]
183
+ params[:password] = @options[:password] if @options[:password]
184
+ params[:merchant_id] = @options[:login]
185
+ end
186
+ params[:vbvEnabled] = '0'
187
+ params[:scEnabled] = '0'
188
+
189
+ params.reject { |k, v| v.blank? }.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join('&')
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,184 @@
1
+ module Spree
2
+ class Gateway::BraintreeGateway < Gateway
3
+ preference :environment, :string
4
+ preference :merchant_id, :string
5
+ preference :merchant_account_id, :string
6
+ preference :public_key, :string
7
+ preference :private_key, :string
8
+ preference :client_side_encryption_key, :text
9
+
10
+ CARD_TYPE_MAPPING = {
11
+ 'American Express' => 'american_express',
12
+ 'Diners Club' => 'diners_club',
13
+ 'Discover' => 'discover',
14
+ 'JCB' => 'jcb',
15
+ 'Laser' => 'laser',
16
+ 'Maestro' => 'maestro',
17
+ 'MasterCard' => 'master',
18
+ 'Solo' => 'solo',
19
+ 'Switch' => 'switch',
20
+ 'Visa' => 'visa'
21
+ }
22
+
23
+ def provider
24
+ provider_instance = super
25
+ Braintree::Configuration.custom_user_agent = "Spree #{Spree.version}"
26
+ Braintree::Configuration.environment = preferred_environment.to_sym
27
+ Braintree::Configuration.merchant_id = preferred_merchant_id
28
+ Braintree::Configuration.public_key = preferred_public_key
29
+ Braintree::Configuration.private_key = preferred_private_key
30
+
31
+ provider_instance
32
+ end
33
+
34
+ def provider_class
35
+ ActiveMerchant::Billing::BraintreeBlueGateway
36
+ end
37
+
38
+ def authorize(money, creditcard, options = {})
39
+ adjust_options_for_braintree(creditcard, options)
40
+
41
+ if creditcard.gateway_payment_profile_id
42
+ payment_method = creditcard.gateway_payment_profile_id
43
+ options[:payment_method_token] = true
44
+ else
45
+ payment_method = creditcard.gateway_customer_profile_id || creditcard
46
+ end
47
+
48
+ provider.authorize(money, payment_method, options)
49
+ end
50
+
51
+ def capture(amount, authorization_code, ignored_options = {})
52
+ provider.capture(amount, authorization_code)
53
+ end
54
+
55
+ def create_profile(payment)
56
+ if payment.source.gateway_customer_profile_id.nil?
57
+ response = provider.store(payment.source, options_for_payment(payment))
58
+
59
+ if response.success?
60
+ payment.source.update_attributes!(:gateway_customer_profile_id => response.params['customer_vault_id'])
61
+ cc = response.params['braintree_customer'].fetch('credit_cards',[]).first
62
+ update_card_number(payment.source, cc) if cc
63
+ else
64
+ payment.send(:gateway_error, response.message)
65
+ end
66
+ end
67
+ end
68
+
69
+ def update_card_number(source, cc)
70
+ last_4 = cc['last_4']
71
+ source.last_digits = last_4 if last_4
72
+ source.gateway_payment_profile_id = cc['token']
73
+ source.cc_type = CARD_TYPE_MAPPING[cc['card_type']] if cc['card_type']
74
+ source.save!
75
+ end
76
+
77
+ def credit(*args)
78
+ if args.size == 4
79
+ # enables ability to refund instead of credit
80
+ args.slice!(1,1)
81
+ credit_without_payment_profiles(*args)
82
+ elsif args.size == 3
83
+ credit_without_payment_profiles(*args)
84
+ else
85
+ raise ArgumentError, "Expected 3 or 4 arguments, received #{args.size}"
86
+ end
87
+ end
88
+
89
+ # Braintree now disables credits by default, see https://www.braintreepayments.com/docs/ruby/transactions/credit
90
+ def credit_with_payment_profiles(amount, payment, response_code, option)
91
+ provider.credit(amount, payment)
92
+ end
93
+
94
+ def credit_without_payment_profiles(amount, response_code, options)
95
+ provider # braintree provider needs to be called here to properly configure braintree gem.
96
+ transaction = ::Braintree::Transaction.find(response_code)
97
+ if BigDecimal.new(amount.to_s) == (transaction.amount * 100)
98
+ provider.refund(response_code)
99
+ elsif BigDecimal.new(amount.to_s) < (transaction.amount * 100) # support partial refunds
100
+ provider.refund(amount, response_code)
101
+ else
102
+ raise NotImplementedError
103
+ end
104
+ end
105
+
106
+ def payment_profiles_supported?
107
+ true
108
+ end
109
+
110
+ def purchase(money, creditcard, options = {})
111
+ authorize(money, creditcard, options.merge(:submit_for_settlement => true))
112
+ end
113
+
114
+ def void(response_code, *ignored_options)
115
+ provider.void(response_code)
116
+ end
117
+
118
+ def options
119
+ h = super
120
+ # We need to add merchant_account_id only if present when creating BraintreeBlueGateway
121
+ # Remove it since it is always part of the preferences hash.
122
+ if h[:merchant_account_id].blank?
123
+ h.delete(:merchant_account_id)
124
+ end
125
+ h
126
+ end
127
+
128
+ def cancel(response_code)
129
+ provider
130
+ transaction = ::Braintree::Transaction.find(response_code)
131
+ # From: https://www.braintreepayments.com/docs/ruby/transactions/refund
132
+ # "A transaction can be refunded if its status is settled or settling.
133
+ # If the transaction has not yet begun settlement, it should be voided instead of refunded.
134
+ if transaction.status == Braintree::Transaction::Status::SubmittedForSettlement
135
+ provider.void(response_code)
136
+ else
137
+ provider.refund(response_code)
138
+ end
139
+ end
140
+
141
+ protected
142
+
143
+ def adjust_billing_address(creditcard, options)
144
+ if creditcard.gateway_customer_profile_id
145
+ options.delete(:billing_address)
146
+ end
147
+ end
148
+
149
+ def adjust_options_for_braintree(creditcard, options)
150
+ adjust_billing_address(creditcard, options)
151
+ end
152
+
153
+ def options_for_payment(p)
154
+ o = Hash.new
155
+ o[:email] = p.order.email
156
+
157
+ if p.source.gateway_customer_profile_id.present?
158
+ o[:customer] = p.source.gateway_customer_profile_id
159
+ end
160
+
161
+ if p.order.bill_address
162
+ bill_addr = p.order.bill_address
163
+
164
+ o[:first_name] = bill_addr.firstname
165
+ o[:last_name] = bill_addr.lastname
166
+
167
+ o[:billing_address] = {
168
+ address1: bill_addr.address1,
169
+ address2: bill_addr.address2,
170
+ company: bill_addr.company,
171
+ city: bill_addr.city,
172
+ state: bill_addr.state ? bill_addr.state.name : bill_addr.state_name,
173
+ country_code_alpha3: bill_addr.country.iso3,
174
+ zip: bill_addr.zipcode
175
+ }
176
+ end
177
+
178
+ o[:verify_card] = "true"
179
+
180
+ return o
181
+ end
182
+
183
+ end
184
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ class Gateway::CardSave < Gateway
3
+ preference :login, :string
4
+ preference :password, :string
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::CardSaveGateway
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Spree
2
+ class Gateway::DataCash < Gateway
3
+ preference :login, :string
4
+ preference :password, :string
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::DataCashGateway
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ module Spree
2
+ class Gateway::Eway < Gateway
3
+ preference :login, :string
4
+
5
+ # Note: EWay supports purchase method only (no authorize method).
6
+ def auto_capture?
7
+ true
8
+ end
9
+
10
+ def provider_class
11
+ ActiveMerchant::Billing::EwayGateway
12
+ end
13
+
14
+ def options_with_test_preference
15
+ options_without_test_preference.merge(:test => self.preferred_test_mode)
16
+ end
17
+
18
+ alias_method_chain :options, :test_preference
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module Spree
2
+ class Gateway::Linkpoint < Gateway
3
+ preference :login, :string
4
+ preference :pem, :text
5
+
6
+ def provider_class
7
+ ActiveMerchant::Billing::LinkpointGateway
8
+ end
9
+
10
+ [:authorize, :purchase, :capture, :void, :credit].each do |method|
11
+ define_method(method) do |*args|
12
+ options = add_discount_to_subtotal(args.extract_options!)
13
+ provider.public_send(method, *args << options)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ # Linkpoint ignores the discount, but it will return an error if the
20
+ # chargetotal is different from the sum of the subtotal, tax and
21
+ # shipping totals.
22
+ def add_discount_to_subtotal(options)
23
+ subtotal = options.fetch(:subtotal)
24
+ discount = options.fetch(:discount)
25
+ options.merge(subtotal: subtotal + discount, discount: 0)
26
+ end
27
+ end
28
+ end