atmos-braintree_transparent_redirect_slice 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. data/LICENSE +20 -0
  2. data/README +41 -0
  3. data/Rakefile +65 -0
  4. data/TODO +15 -0
  5. data/app/controllers/application.rb +5 -0
  6. data/app/controllers/credit_cards.rb +54 -0
  7. data/app/controllers/main.rb +7 -0
  8. data/app/controllers/payments.rb +17 -0
  9. data/app/helpers/application_helper.rb +63 -0
  10. data/app/helpers/credit_cards_helper.rb +15 -0
  11. data/app/models/braintree/gateway_request.rb +55 -0
  12. data/app/models/braintree/gateway_response.rb +101 -0
  13. data/app/models/braintree/query.rb +46 -0
  14. data/app/models/braintree/transaction_info.rb +20 -0
  15. data/app/models/credit_card.rb +18 -0
  16. data/app/models/credit_card_info.rb +31 -0
  17. data/app/models/credit_card_invoice.rb +15 -0
  18. data/app/views/braintree_transparent_redirect_slice/credit_cards/_form.html.haml +27 -0
  19. data/app/views/braintree_transparent_redirect_slice/credit_cards/_gateway_request.html.haml +6 -0
  20. data/app/views/braintree_transparent_redirect_slice/credit_cards/destroy.html.haml +8 -0
  21. data/app/views/braintree_transparent_redirect_slice/credit_cards/edit.html.haml +8 -0
  22. data/app/views/braintree_transparent_redirect_slice/credit_cards/index.html.haml +26 -0
  23. data/app/views/braintree_transparent_redirect_slice/credit_cards/new.html.haml +9 -0
  24. data/app/views/braintree_transparent_redirect_slice/credit_cards/show.html.haml +33 -0
  25. data/app/views/braintree_transparent_redirect_slice/payments/index.html.haml +2 -0
  26. data/app/views/braintree_transparent_redirect_slice/payments/new.html.haml +21 -0
  27. data/app/views/layout/braintree_transparent_redirect_slice.html.haml +26 -0
  28. data/app/views/main/index.html.haml +1 -0
  29. data/lib/braintree_transparent_redirect_slice.rb +103 -0
  30. data/lib/braintree_transparent_redirect_slice/merbtasks.rb +103 -0
  31. data/lib/braintree_transparent_redirect_slice/slicetasks.rb +20 -0
  32. data/lib/braintree_transparent_redirect_slice/spectasks.rb +53 -0
  33. data/lib/braintree_transparent_redirect_slice/version.rb +3 -0
  34. data/public/javascripts/master.js +6 -0
  35. data/public/stylesheets/master.css +153 -0
  36. data/spec/fixtures/user.rb +24 -0
  37. data/spec/models/credit_card_info_spec.rb +30 -0
  38. data/spec/requests/credit_cards/adding_a_card_spec.rb +110 -0
  39. data/spec/requests/credit_cards/deleting_a_card_spec.rb +19 -0
  40. data/spec/requests/credit_cards/updating_a_card_spec.rb +56 -0
  41. data/spec/requests/main_spec.rb +14 -0
  42. data/spec/requests/payments/issuing_a_transaction_spec.rb +49 -0
  43. data/spec/spec_helper.rb +127 -0
  44. data/spec/spec_helpers/braintree/api_helper.rb +14 -0
  45. data/spec/spec_helpers/edit_form_helper.rb +34 -0
  46. data/stubs/app/controllers/application.rb +2 -0
  47. data/stubs/app/controllers/main.rb +2 -0
  48. metadata +212 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Corey Donohoe
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,41 @@
1
+ BraintreeTransparentRedirectSlice
2
+ =================================
3
+
4
+ A slice for the Merb framework that lets you store shit securely in Braintree's
5
+ vault (http://dev.braintreepaymentsolutions.com/vault/transparent-redirect/).
6
+ You get tokens for user's sensitive info and you can charge against those
7
+ tokens. The information is securely stored in Braintree's vault to allow you
8
+ to charge against credit cards without having ANY sensitive information pass
9
+ through your controller actions or logs. It's quite cool.
10
+
11
+ ------------------------------------------------------------------------------
12
+
13
+ Installation
14
+ ------------
15
+ % grep braintree_transparent_redirect config/*.rb
16
+ dependency "braintree_transparent_redirect_slice", "=1.0.7.1"
17
+
18
+ ------------------------------------------------------------------------------
19
+ # example: /:controller/:action/:id
20
+ slice(:BraintreeTransparentRedirectSlice)
21
+
22
+ # example: /billing/controller/:action/:id
23
+ add_slice(:braintree_transparent_redirect_slice, :name_prefix => nil, :path_prefix => "billing")
24
+
25
+ Normally you should also run the following rake task:
26
+ rake slices:braintree_transparent_redirect_slice:install
27
+ ------------------------------------------------------------------------------
28
+
29
+ Goals
30
+ -----
31
+ * actually work, as in all specs passing
32
+ * sanitize and abstract the braintree models into a gem you can use outside of merb
33
+ * hook into merb-auth so you can associate vault items into more than just the user.
34
+ i.e. instance method on the session perhaps
35
+ * support bank account info, not just credit cards
36
+
37
+ Developing
38
+ ----------
39
+ % thor merb:gem:install
40
+ % cp config/braintree.yml-example config/braintree.yml
41
+ % bin/rake
@@ -0,0 +1,65 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ require 'merb-core'
5
+ require 'merb-core/tasks/merb'
6
+ require 'lib/braintree_transparent_redirect_slice/version'
7
+
8
+ GEM_NAME = "braintree_transparent_redirect_slice"
9
+ AUTHOR = "Corey Donohoe"
10
+ EMAIL = "atmos@atmos.org"
11
+ HOMEPAGE = "http://github.com/atmos/braintree_transparent_redirect_slice/"
12
+ SUMMARY = "Merb Slice that allows you to process stuff with braintree, without storing credit cards and shit"
13
+ GEM_VERSION = BraintreeTransparentRedirectSlice::VERSION
14
+
15
+ spec = Gem::Specification.new do |s|
16
+ s.rubyforge_project = 'merb'
17
+ s.name = GEM_NAME
18
+ s.version = GEM_VERSION
19
+ s.platform = Gem::Platform::RUBY
20
+ s.has_rdoc = true
21
+ s.extra_rdoc_files = ["README", "LICENSE", 'TODO']
22
+ s.summary = SUMMARY
23
+ s.description = s.summary
24
+ s.author = AUTHOR
25
+ s.email = EMAIL
26
+ s.homepage = HOMEPAGE
27
+ s.add_dependency 'merb-slices', '>= 1.0.7.1'
28
+ s.add_dependency 'libxml-ruby', '=0.9.7'
29
+ s.add_dependency 'dm-core', '>=0.9.8'
30
+ s.add_dependency 'dm-validations', '>=0.9.8'
31
+ s.add_dependency 'merb-auth-core'
32
+ s.add_dependency 'merb-auth-more'
33
+ s.add_dependency 'merb-haml'
34
+ s.add_dependency 'merb-helpers'
35
+ s.add_dependency 'merb-assets'
36
+ s.add_dependency 'merb-action-args'
37
+ s.require_path = 'lib'
38
+ s.files = %w(LICENSE README Rakefile TODO) + Dir.glob("{lib,spec,app,public,stubs}/**/*")
39
+ end
40
+
41
+ Rake::GemPackageTask.new(spec) do |pkg|
42
+ pkg.gem_spec = spec
43
+ end
44
+
45
+ desc "Install the gem"
46
+ task :install do
47
+ Merb::RakeHelper.install(GEM_NAME, :version => GEM_VERSION)
48
+ end
49
+
50
+ desc "Uninstall the gem"
51
+ task :uninstall do
52
+ Merb::RakeHelper.uninstall(GEM_NAME, :version => GEM_VERSION)
53
+ end
54
+
55
+ desc "Create a gemspec file"
56
+ task :gemspec do
57
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
58
+ file.puts spec.to_ruby
59
+ end
60
+ end
61
+
62
+ require 'spec/rake/spectask'
63
+ require 'merb-core/test/tasks/spectasks'
64
+ desc 'Default: run spec examples'
65
+ task :default => 'spec'
data/TODO ADDED
@@ -0,0 +1,15 @@
1
+ TODO:
2
+
3
+ - Fix BraintreeTransparentRedirectSlice.description and BraintreeTransparentRedirectSlice.version
4
+ - Fix LICENSE with your name
5
+ - Fix Rakefile with your name and contact info
6
+ - Add your code to lib/braintree_transparent_redirect_slice.rb
7
+ - Add your Merb rake tasks to lib/braintree_transparent_redirect_slice/merbtasks.rb
8
+
9
+ Remove anything that you don't need:
10
+
11
+ - app/controllers/main.rb BraintreeTransparentRedirectSlice::Main controller
12
+ - app/views/layout/braintree_transparent_redirect_slice.html.erb
13
+ - spec/controllers/main_spec.rb controller specs
14
+ - public/* any public files
15
+ - stubs/* any stub files
@@ -0,0 +1,5 @@
1
+ class BraintreeTransparentRedirectSlice::Application < Merb::Controller
2
+ before :ensure_authenticated
3
+ include Merb::BraintreeTransparentRedirectSlice::CreditCardsHelper
4
+ controller_for_slice
5
+ end
@@ -0,0 +1,54 @@
1
+ class BraintreeTransparentRedirectSlice::CreditCards < BraintreeTransparentRedirectSlice::Application
2
+ def index
3
+ render
4
+ end
5
+
6
+ def new
7
+ @credit_card = CreditCard.new
8
+ @credit_card_info = @credit_card.info
9
+
10
+ unless message[:transaction_id].nil?
11
+ @credit_card_info = Braintree::TransactionInfo.new(message[:transaction_id])
12
+ end
13
+
14
+ @gateway_request = Braintree::GatewayRequest.new(:orderid => Digest::SHA1.hexdigest(Time.now.to_s))
15
+ render
16
+ end
17
+
18
+ def new_response
19
+ @gateway_response = Braintree::GatewayResponse.validate(params)
20
+ if error = @gateway_response.error
21
+ redirect(slice_url(:new_credit_card), :message => {:notice => error, :transaction_id => params[:transactionid]})
22
+ else
23
+ session.user.credit_cards.create(:token => @gateway_response.customer_vault_id)
24
+ redirect(slice_url(:credit_cards), :message => {:notice => 'Successfully stored your card info securely.'})
25
+ end
26
+ end
27
+
28
+ def edit(id)
29
+ fetch_credit_card(id)
30
+ @credit_card_info = @credit_card.info
31
+ @gateway_request = Braintree::GatewayRequest.new
32
+ render
33
+ end
34
+
35
+ def edit_response(id)
36
+ @gateway_response = Braintree::GatewayResponse.validate(params)
37
+ if error = @gateway_response.error
38
+ redirect(slice_url(:edit_credit_card, id), :message => {:notice => error})
39
+ else
40
+ redirect(slice_url(:credit_cards), :message => {:notice => 'Successfully updated your info in the vault.'})
41
+ end
42
+ end
43
+
44
+ def show(id)
45
+ fetch_credit_card(id)
46
+ render
47
+ end
48
+
49
+ def destroy(id)
50
+ fetch_credit_card(id)
51
+ @gateway_request = Braintree::GatewayRequest.new
52
+ render
53
+ end
54
+ end
@@ -0,0 +1,7 @@
1
+ class BraintreeTransparentRedirectSlice::Main < BraintreeTransparentRedirectSlice::Application
2
+
3
+ def index
4
+ render
5
+ end
6
+
7
+ end
@@ -0,0 +1,17 @@
1
+ class BraintreeTransparentRedirectSlice::Payments < BraintreeTransparentRedirectSlice::Application
2
+ def new(credit_card_id)
3
+ fetch_credit_card(credit_card_id)
4
+
5
+ @gateway_request = Braintree::GatewayRequest.new(:amount => '10.00')
6
+ render
7
+ end
8
+
9
+ def new_response(credit_card_id)
10
+ @gateway_response = Braintree::GatewayResponse.validate(params)
11
+ if error = @gateway_response.error
12
+ redirect(slice_url(:new_credit_card_payment), :message => {:notice => error})
13
+ else
14
+ redirect(slice_url(:credit_card, credit_card_id), :message => {:notice => 'Successfully charged your Credit Card.'})
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,63 @@
1
+ module Merb
2
+ module BraintreeTransparentRedirectSlice
3
+ module ApplicationHelper
4
+ # @param *segments<Array[#to_s]> Path segments to append.
5
+ #
6
+ # @return <String>
7
+ # A path relative to the public directory, with added segments.
8
+ def image_path(*segments)
9
+ public_path_for(:image, *segments)
10
+ end
11
+
12
+ # @param *segments<Array[#to_s]> Path segments to append.
13
+ #
14
+ # @return <String>
15
+ # A path relative to the public directory, with added segments.
16
+ def javascript_path(*segments)
17
+ public_path_for(:javascript, *segments)
18
+ end
19
+
20
+ # @param *segments<Array[#to_s]> Path segments to append.
21
+ #
22
+ # @return <String>
23
+ # A path relative to the public directory, with added segments.
24
+ def stylesheet_path(*segments)
25
+ public_path_for(:stylesheet, *segments)
26
+ end
27
+
28
+ # Construct a path relative to the public directory
29
+ #
30
+ # @param <Symbol> The type of component.
31
+ # @param *segments<Array[#to_s]> Path segments to append.
32
+ #
33
+ # @return <String>
34
+ # A path relative to the public directory, with added segments.
35
+ def public_path_for(type, *segments)
36
+ ::BraintreeTransparentRedirectSlice.public_path_for(type, *segments)
37
+ end
38
+
39
+ # Construct an app-level path.
40
+ #
41
+ # @param <Symbol> The type of component.
42
+ # @param *segments<Array[#to_s]> Path segments to append.
43
+ #
44
+ # @return <String>
45
+ # A path within the host application, with added segments.
46
+ def app_path_for(type, *segments)
47
+ ::BraintreeTransparentRedirectSlice.app_path_for(type, *segments)
48
+ end
49
+
50
+ # Construct a slice-level path.
51
+ #
52
+ # @param <Symbol> The type of component.
53
+ # @param *segments<Array[#to_s]> Path segments to append.
54
+ #
55
+ # @return <String>
56
+ # A path within the slice source (Gem), with added segments.
57
+ def slice_path_for(type, *segments)
58
+ ::BraintreeTransparentRedirectSlice.slice_path_for(type, *segments)
59
+ end
60
+
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,15 @@
1
+ module Merb
2
+ module BraintreeTransparentRedirectSlice
3
+ module CreditCardsHelper
4
+ def fetch_credit_card(id)
5
+ @credit_card = CreditCard.get(id)
6
+ raise Merb::ControllerExceptions::NotFound if @credit_card.nil?
7
+ raise Merb::ControllerExceptions::Unauthorized unless @credit_card.user_id == session.user.id
8
+ end
9
+
10
+ def braintree_date_reformatter(str)
11
+ DateTime.parse(str).strftime('%Y/%m/%d %H:%M:%S')
12
+ end
13
+ end
14
+ end
15
+ end # Merb
@@ -0,0 +1,55 @@
1
+ module Braintree
2
+ class GatewayRequest
3
+ attr_accessor :orderid, :amount, :key, :key_id, :time, :response_url,
4
+ :type, :customer_vault, :customer_vault_id
5
+
6
+ attr_reader :hash
7
+
8
+ def initialize(attributes = nil)
9
+ attributes.each { |k,v| self.send("#{k}=", v) } unless attributes.nil?
10
+ self.key, self.key_id = BraintreeTransparentRedirectSlice.config[:key], BraintreeTransparentRedirectSlice.config[:key_id]
11
+ self.time = self.class.formatted_time_value
12
+ end
13
+
14
+ def hash
15
+ items = if customer_vault_id.nil?
16
+ [orderid, amount, time, BraintreeTransparentRedirectSlice.config[:key]]
17
+ else
18
+ [orderid, amount, customer_vault_id, time, BraintreeTransparentRedirectSlice.config[:key]]
19
+ end
20
+ Digest::MD5.hexdigest(items.join('|'))
21
+ end
22
+
23
+ def self.formatted_time_value
24
+ Time.now.getutc.strftime("%Y%m%d%H%M%S")
25
+ end
26
+
27
+ def hash_attributes
28
+ { 'orderid' => orderid, 'amount' => amount, 'key_id' => key_id,
29
+ 'time' => time, 'hash' => hash, 'customer_vault_id' => customer_vault_id }
30
+ end
31
+
32
+ def post(params)
33
+ uri = Addressable::URI.parse(BraintreeTransparentRedirectSlice.config[:transact_api_url])
34
+
35
+ server = Net::HTTP.new(uri.host, 443)
36
+ server.use_ssl = true
37
+ server.read_timeout = 20
38
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
39
+
40
+ resp = server.start do |http|
41
+ req = Net::HTTP::Post.new(uri.path)
42
+ req.set_form_data(hash_attributes.merge(params))
43
+ http.request(req)
44
+ end
45
+ case resp
46
+ when Net::HTTPRedirection
47
+ Addressable::URI.parse(resp.header['Location'])
48
+ when Net::HTTPSuccess
49
+ resp
50
+ else
51
+ resp.error!
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,101 @@
1
+ require 'digest/md5'
2
+
3
+ module Braintree
4
+ class GatewayResponse
5
+ def self.validate(params)
6
+ new(params).validate
7
+ end
8
+
9
+ def initialize(params)
10
+ @time, @hash, @amount = params.values_at(:time, :hash, :amount)
11
+ @transaction_id, @order_id, @customer_vault_id = params.values_at(:transactionid, :orderid, :customer_vault_id)
12
+ @response, @response_text = params.values_at(:response, :responsetext)
13
+ @avs_response, @cvv_response = params.values_at(:avsresponse, :cvvresponse)
14
+ end
15
+
16
+ attr_reader :time, :hash, :amount, :error,
17
+ :transaction_id, :order_id, :customer_vault_id,
18
+ :response, :response_text,
19
+ :avs_response, :cvv_response
20
+ # :authcode
21
+ # :username
22
+ # :type
23
+ # :response_code
24
+ # :full_response
25
+
26
+ def validate
27
+ raise Merb::ControllerExceptions::Unauthorized, "Hashes did not match" unless matching_hash?
28
+ if approved?
29
+ if cvv_matches?
30
+ true
31
+ else
32
+ @error = cvv_response_message
33
+ end
34
+ else
35
+ @error = response_text
36
+ end
37
+ self
38
+ end
39
+
40
+ # The hash sent with the Gateway Response should equal a hash that can get
41
+ # generated using the key and the sent parameters.
42
+ def matching_hash?
43
+ hash == generated_hash
44
+ end
45
+
46
+ # Takes the values of the Gateway Response and generates a hash from them using
47
+ # MD5 and format listed in the documentation.
48
+ def generated_hash
49
+ items = [order_id, amount, response]
50
+ items += [transaction_id, avs_response, cvv_response]
51
+ items << customer_vault_id if customer_vault_id
52
+ items += [time, BraintreeTransparentRedirectSlice.config[:key]]
53
+ Digest::MD5.hexdigest(items.join('|'))
54
+ end
55
+
56
+ def approved?
57
+ response == "1"
58
+ end
59
+
60
+ # AVS_RESPONSE_CODES
61
+ def avs_matches?
62
+ avs_response.include?("Y")
63
+ end
64
+
65
+ # CVV_RESPONSE_CODES
66
+ def cvv_matches?
67
+ cvv_response == "M"
68
+ end
69
+
70
+ # A string representation of the response status given as a number.
71
+ def response_message
72
+ case response
73
+ when "1"
74
+ "Approved"
75
+ when "2"
76
+ "Declined"
77
+ when "3"
78
+ "Error"
79
+ else
80
+ raise "Unknown response: #{response.inspect}"
81
+ end
82
+ end
83
+
84
+ def cvv_response_message
85
+ case cvv_response
86
+ when "M"
87
+ "CVV2/CVC2 Match"
88
+ when "N"
89
+ "CVV2/CVC2 No Match"
90
+ when "P"
91
+ "Not Processed"
92
+ when "S"
93
+ "Merchant has indicated the CVV2/CVC2 is not present on card"
94
+ when "U"
95
+ "Issuer is not certified and/or has not provided Visa encryption keys"
96
+ else
97
+ #raise "Unknown CVV response: #{cvv_response.inspect}"
98
+ end
99
+ end
100
+ end
101
+ end