solidus_braintree 0.1.0

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: af19cf56975489c4e80eb66b7cacce68f025d395
4
+ data.tar.gz: 3a2a4764fbe429a4029fa6818dadf017fe244d1a
5
+ SHA512:
6
+ metadata.gz: 96f6ea5668b4858ee83862fe80191fd2c654ba91eb846d90879acd0721a8085215f86c82c9c12c3248f9b12f04c3d3c21da4038da7fa32937d6ebb21d21df275
7
+ data.tar.gz: f9ef7734bd60bfa523e73a87eb47f13e6e8c5a8885fea7234b570366e5ffadba94c28d3d46b6a9b15a7260be3fa2155733ee3a1d1cb5c45b9861a29213c20114
@@ -0,0 +1,10 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ /spec/dummy
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.6
4
+ before_install: gem install bundler -v 1.10.5
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in solidus_braintree.gemspec
4
+ #gem "solidus", github: "solidusio/solidus", branch: "master"
5
+ gemspec
@@ -0,0 +1,70 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ # Note: The cmd option is now required due to the increasing number of ways
19
+ # rspec may be run, below are examples of the most common uses.
20
+ # * bundler: 'bundle exec rspec'
21
+ # * bundler binstubs: 'bin/rspec'
22
+ # * spring: 'bin/rspec' (This will use spring if running and you have
23
+ # installed the spring binstubs per the docs)
24
+ # * zeus: 'zeus rspec' (requires the server to be started separately)
25
+ # * 'just' rspec: 'rspec'
26
+
27
+ guard :rspec, cmd: "bundle exec rspec" do
28
+ require "guard/rspec/dsl"
29
+ dsl = Guard::RSpec::Dsl.new(self)
30
+
31
+ # Feel free to open issues for suggestions and improvements
32
+
33
+ # RSpec files
34
+ rspec = dsl.rspec
35
+ watch(rspec.spec_helper) { rspec.spec_dir }
36
+ watch(rspec.spec_support) { rspec.spec_dir }
37
+ watch(rspec.spec_files)
38
+
39
+ # Ruby files
40
+ ruby = dsl.ruby
41
+ dsl.watch_spec_files_for(ruby.lib_files)
42
+
43
+ # Rails files
44
+ rails = dsl.rails(view_extensions: %w(erb haml slim))
45
+ dsl.watch_spec_files_for(rails.app_files)
46
+ dsl.watch_spec_files_for(rails.views)
47
+
48
+ watch(rails.controllers) do |m|
49
+ [
50
+ rspec.spec.("routing/#{m[1]}_routing"),
51
+ rspec.spec.("controllers/#{m[1]}_controller"),
52
+ rspec.spec.("acceptance/#{m[1]}")
53
+ ]
54
+ end
55
+
56
+ # Rails config changes
57
+ watch(rails.spec_helper) { rspec.spec_dir }
58
+ watch(rails.routes) { "#{rspec.spec_dir}/routing" }
59
+ watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" }
60
+
61
+ # Capybara features specs
62
+ watch(rails.view_dirs) { |m| rspec.spec.("features/#{m[1]}") }
63
+ watch(rails.layouts) { |m| rspec.spec.("features/#{m[1]}") }
64
+
65
+ # Turnip features and steps
66
+ watch(%r{^spec/acceptance/(.+)\.feature$})
67
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m|
68
+ Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance"
69
+ end
70
+ end
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Jack Chu
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
13
+ all 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
21
+ THE SOFTWARE.
@@ -0,0 +1,41 @@
1
+ # SolidusBraintree
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/solidus_braintree`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'solidus_braintree'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install solidus_braintree
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/solidus_braintree.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require 'spree/testing_support/common_rake'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ Bundler::GemHelper.install_tasks
8
+
9
+ task default: :spec
10
+
11
+ desc "Generates a dummy app for testing"
12
+ task :test_app do
13
+ ENV['LIB_NAME'] = 'solidus_braintree'
14
+ Rake::Task['common:test_app'].invoke
15
+ end
@@ -0,0 +1,11 @@
1
+ class Spree::Api::BraintreeClientTokenController < Spree::Api::BaseController
2
+ def create
3
+ if params[:payment_method_id]
4
+ gateway = Solidus::Gateway::BraintreeGateway.find_by!(id: params[:payment_method_id])
5
+ else
6
+ gateway = Solidus::Gateway::BraintreeGateway.find_by!(active: true)
7
+ end
8
+
9
+ render json: { client_token: gateway.generate_client_token, payment_method_id: gateway.id }
10
+ end
11
+ end
@@ -0,0 +1,6 @@
1
+ module PaymentBraintreeNonceConcern
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ attr_accessor :payment_method_nonce
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module PermittedAttributesConcern
2
+ def payment_attributes
3
+ super | [:payment_method_nonce]
4
+ end
5
+ end
@@ -0,0 +1,12 @@
1
+ module SkipRequireCardNumbersConcern
2
+ extend ActiveSupport::Concern
3
+ included do
4
+ prepend(InstanceMethods)
5
+ end
6
+
7
+ module InstanceMethods
8
+ def require_card_numbers?
9
+ super && !self.payment_method.kind_of?(Solidus::Gateway::BraintreeGateway)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1 @@
1
+ Spree::CreditCard.include SolidusBraintree::SkipRequireCardNumbersConcern
@@ -0,0 +1 @@
1
+ Spree::Payment.include SolidusBraintree::PaymentBraintreeNonceConcern
@@ -0,0 +1 @@
1
+ Spree::PermittedAttributes.singleton_class.prepend SolidusBraintree::PermittedAttributesConcern
@@ -0,0 +1,227 @@
1
+ require "braintree"
2
+
3
+ module Solidus
4
+ class Gateway::BraintreeGateway < ::Spree::Gateway
5
+ preference :environment, :string
6
+ preference :merchant_id, :string
7
+ preference :public_key, :string
8
+ preference :private_key, :string
9
+ preference :always_send_bill_address, :boolean, default: false
10
+
11
+ CARD_TYPE_MAPPING = {
12
+ 'American Express' => 'american_express',
13
+ 'Diners Club' => 'diners_club',
14
+ 'Discover' => 'discover',
15
+ 'JCB' => 'jcb',
16
+ 'Laser' => 'laser',
17
+ 'Maestro' => 'maestro',
18
+ 'MasterCard' => 'master',
19
+ 'Solo' => 'solo',
20
+ 'Switch' => 'switch',
21
+ 'Visa' => 'visa',
22
+ }
23
+
24
+ def gateway_options
25
+ {
26
+ environment: preferred_environment.to_sym,
27
+ merchant_id: preferred_merchant_id,
28
+ public_key: preferred_public_key,
29
+ private_key: preferred_private_key,
30
+ logger: ::Braintree::Configuration.logger.clone,
31
+ }
32
+ end
33
+
34
+ def braintree_gateway
35
+ @braintree_gateway ||= ::Braintree::Gateway.new(gateway_options)
36
+ end
37
+
38
+ def payment_profiles_supported?
39
+ true
40
+ end
41
+
42
+ def generate_client_token(options = {})
43
+ braintree_gateway.client_token.generate(options)
44
+ end
45
+
46
+ def create_profile(payment)
47
+ source = payment.source
48
+
49
+ return if source.gateway_customer_profile_id.present? || payment.payment_method_nonce.nil?
50
+
51
+ user = payment.order.user
52
+ address = (payment.source.address || payment.order.bill_address).try(:active_merchant_hash)
53
+
54
+ params = {
55
+ first_name: source.first_name,
56
+ last_name: source.last_name,
57
+ email: user.email,
58
+ credit_card: {
59
+ cardholder_name: source.name,
60
+ billing_address: map_address(address),
61
+ payment_method_nonce: payment.payment_method_nonce,
62
+ options: {
63
+ verify_card: true,
64
+ },
65
+ },
66
+ }
67
+
68
+ result = braintree_gateway.customer.create(params)
69
+
70
+ if result.success?
71
+ card = result.customer.payment_methods.last
72
+ source.tap do |solidus_cc|
73
+ if card.is_a?(::Braintree::PayPalAccount)
74
+ solidus_cc.cc_type = 'paypal'
75
+ solidus_cc.name = card.email
76
+ else
77
+ solidus_cc.name = card.cardholder_name
78
+ solidus_cc.cc_type = card.card_type.downcase
79
+ solidus_cc.month = card.expiration_month
80
+ solidus_cc.year = card.expiration_year
81
+ solidus_cc.last_digits = card.last_4
82
+ end
83
+ solidus_cc.payment_method = self
84
+ solidus_cc.gateway_customer_profile_id = result.customer.id
85
+ solidus_cc.gateway_payment_profile_id = card.token
86
+ end
87
+ source.save!
88
+ else
89
+ raise ::Spree::Core::GatewayError, result.message
90
+ end
91
+ end
92
+
93
+ def supports?(payment)
94
+ true
95
+ end
96
+
97
+ def provider_class
98
+ self
99
+ end
100
+
101
+ def authorize(cents, creditcard, options = {})
102
+ result = braintree_gateway.transaction.sale(transaction_authorize_or_purchase_params(cents, creditcard, options))
103
+ handle_result(result)
104
+ end
105
+
106
+ def purchase(cents, creditcard, options = {})
107
+ params = transaction_authorize_or_purchase_params(cents, creditcard, options)
108
+ params[:options][:submit_for_settlement] = true
109
+ result = braintree_gateway.transaction.sale(params)
110
+ handle_result(result)
111
+ end
112
+
113
+ def capture(money, authorization_code, options = {})
114
+ result = braintree_gateway.transaction.submit_for_settlement(authorization_code, amount(money))
115
+ handle_result(result)
116
+ end
117
+
118
+ def void(authorization_code, source = {}, options = {})
119
+ result = braintree_gateway.transaction.void(authorization_code)
120
+
121
+ handle_result(result)
122
+ end
123
+
124
+ def credit(cents, source, authorization_code, options = {})
125
+ result = braintree_gateway.transaction.refund(authorization_code, amount(cents))
126
+ handle_result(result)
127
+ end
128
+
129
+ def voidable?(response_code)
130
+ transaction = braintree_gateway.transaction.find(response_code)
131
+ [
132
+ ::Braintree::Transaction::Status::SubmittedForSettlement,
133
+ ::Braintree::Transaction::Status::Authorized,
134
+ ].include?(transaction.status)
135
+ end
136
+
137
+ private
138
+ def message_from_result(result)
139
+ if result.success?
140
+ "OK"
141
+ elsif result.errors.count == 0 && result.credit_card_verification
142
+ "Processor declined: #{result.credit_card_verification.processor_response_text} (#{result.credit_card_verification.processor_response_code})"
143
+ elsif result.errors.count == 0 && result.transaction
144
+ result.transaction.status
145
+ else
146
+ result.errors.map { |e| "#{e.message} (#{e.code})" }.join(" ")
147
+ end
148
+ end
149
+
150
+ def handle_result(result)
151
+ ActiveMerchant::Billing::Response.new(
152
+ result.success?,
153
+ message_from_result(result),
154
+ {},
155
+ { authorization: (result.transaction.id if result.success?) }
156
+ )
157
+ end
158
+
159
+ def map_address(addr)
160
+ {
161
+ street_address: addr[:address1],
162
+ extended_address: addr[:address2],
163
+ locality: addr[:city],
164
+ region: addr[:state],
165
+ country_code_alpha2: addr[:country],
166
+ postal_code: addr[:zip],
167
+ }
168
+ end
169
+
170
+ def amount(cents)
171
+ sprintf("%.2f", cents.to_f / 100)
172
+ end
173
+
174
+ def transaction_authorize_or_purchase_params(cents, creditcard, options = {})
175
+ params = options.select {|k| %i[
176
+ billing_address_id
177
+ channel
178
+ custom_fields
179
+ descriptor
180
+ device_data
181
+ device_session_id
182
+ merchant_account_id
183
+ options
184
+ order_id
185
+ purchase_order_number
186
+ recurring
187
+ service_fee_amount
188
+ shipping_address_id
189
+ tax_amount
190
+ tax_exempt
191
+ ].include?(k)}
192
+
193
+ params[:options] ||= {}
194
+ params[:amount] = amount(cents)
195
+ params[:channel] ||= "Solidus"
196
+ params[:shipping] = map_address(options[:shipping_address]) if options[:shipping_address]
197
+
198
+ if options[:payment_method_nonce]
199
+ params[:payment_method_nonce] = options[:payment_method_nonce]
200
+ else
201
+ params[:payment_method_token] = creditcard.gateway_payment_profile_id
202
+ end
203
+
204
+ # Send the bill address if we're using a nonce (i.e. doing a one-time
205
+ # payment) or if we're configured to always send the bill address
206
+ if options[:payment_method_nonce] || preferred_always_send_bill_address
207
+ params[:billing] = map_address(options[:billing_address]) if options[:billing_address]
208
+ end
209
+
210
+ # if has profile, set the customer_id to the profile_id and delete the customer key
211
+ if creditcard.try(:gateway_customer_profile_id)
212
+ params[:customer_id] = creditcard.gateway_customer_profile_id
213
+ # if no profile, define the customer key, delete the customer_id because they are
214
+ # mutually exclusive
215
+ else
216
+ params[:customer] = {
217
+ id: options[:customer_id],
218
+ email: options[:customer],
219
+ first_name: creditcard.first_name,
220
+ last_name: creditcard.last_name,
221
+ }
222
+ end
223
+
224
+ params
225
+ end
226
+ end
227
+ end