samurai 0.2.4 → 0.2.5

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,15 @@
1
+ <% transaction ||= Samurai::Transaction.new %>
2
+ <% payment_method ||= Samurai::PaymentMethod.new %>
3
+ <% if transaction.errors.any? || payment_method.errors.any? %>
4
+ <div id="error_explanation">
5
+ <h4>This transaction could not be processed:</h4>
6
+ <ul>
7
+ <% transaction.errors.full_messages.each do |msg| %>
8
+ <li><%= msg %></li>
9
+ <% end %>
10
+ <% payment_method.errors.full_messages.each do |msg| %>
11
+ <li><%= msg %></li>
12
+ <% end %>
13
+ </ul>
14
+ </div>
15
+ <% end %>
@@ -1,5 +1,5 @@
1
1
  <% sandbox ||= false %>
2
- <% payment_method ||= OpenStruct.new %>
2
+ <% payment_method ||= Samurai::PaymentMethod.new %>
3
3
  <form action="<%= Samurai.site %>/payment_methods" method="POST">
4
4
  <fieldset>
5
5
  <input name="redirect_url" type="hidden" value="<%= redirect_url %>" />
@@ -1,16 +1,7 @@
1
1
  <h3><%= transaction.errors.empty? ? 'Successful' : 'Failed' %> <%= transaction.transaction_type.titleize %> Transaction</h3>
2
2
  <h4>Reference ID: <%= transaction.reference_id %></h4>
3
3
 
4
- <% if transaction.errors.any? %>
5
- <div id="error_explanation">
6
- <h4>This transaction could not be processed:</h4>
7
- <ul>
8
- <% transaction.errors.full_messages.each do |msg| %>
9
- <li><%= msg %></li>
10
- <% end %>
11
- </ul>
12
- </div>
13
- <% end %>
4
+ <%= render Samurai::Views.errors :transaction=>transaction %>
14
5
 
15
6
  <p>
16
7
  <strong>Amount:</strong>
@@ -1,5 +1,5 @@
1
1
  <% authenticity_token ||= '' %>
2
- <% transaction ||= OpenStruct.new %>
2
+ <% transaction ||= Samurai::Transaction.new %>
3
3
  <form accept-charset="UTF-8" action="<%= post_url %>" class="new_transaction" id="new_transaction" method="post">
4
4
  <%= authenticity_token %>
5
5
  <input id="processor_token" name="processor_token" type="hidden" value="<%= processor_token %>"/>
data/lib/samurai.rb CHANGED
@@ -30,7 +30,7 @@ module Samurai
30
30
  @@options = (value || {}).reverse_merge(DEFAULT_OPTIONS)
31
31
  Samurai::Base.setup_site!
32
32
  end
33
-
33
+
34
34
  end
35
35
 
36
36
  require 'samurai/cacheable_by_token'
@@ -40,3 +40,5 @@ require 'samurai/payment_method'
40
40
  require 'samurai/transaction'
41
41
  require 'samurai/message'
42
42
  require 'samurai/processor_response'
43
+
44
+ require 'samurai/rails'
data/lib/samurai/base.rb CHANGED
@@ -11,11 +11,18 @@ class Samurai::Base < ActiveResource::Base
11
11
  self.password = Samurai.merchant_password
12
12
  end
13
13
 
14
+ def has_errors?
15
+ respond_to?(:errors) && !errors.empty?
16
+ end
17
+
14
18
  protected
15
19
 
16
20
  def load_attributes_from_response(response)
17
21
  super.tap { |instance| instance.process_response_errors }
18
22
  end
23
+ def self.instantiate_record(record, prefix_options = {})
24
+ super.tap { |instance| instance.send :process_response_errors }
25
+ end
19
26
 
20
27
  def process_response_errors
21
28
  # Do nothing by default, subclasses may override this to process specific error messages
@@ -27,13 +27,38 @@ class Samurai::PaymentMethod < Samurai::Base
27
27
  @custom_data ||= self.custom && (JSON.parse(self.custom) rescue {}).symbolize_keys
28
28
  end
29
29
 
30
+ def process_response_errors
31
+ if respond_to?(:messages) && self.messages
32
+ self.messages.each do |message|
33
+ #if (message.respond_to?(:subclass) && message.subclass == 'error')
34
+ self.errors.add message.context.gsub(/\./, ' '), message.key
35
+ #end
36
+ end
37
+ end
38
+ end
39
+ protected :process_response_errors
30
40
 
31
- require 'pathname'
32
- def self.form_html
33
- File.read(form_partial_path)
41
+ # Initialize the known attributes from the schema as empty strings, so that they can be accessed via method-missing
42
+ KNOWN_ATTRIBUTES = [
43
+ :first_name, :last_name, :address_1, :address_2, :city, :state, :zip,
44
+ :card_number, :cvv, :expiry_month, :expiry_year
45
+ ]
46
+ EMPTY_ATTRIBUTES = KNOWN_ATTRIBUTES.inject({}) {|h, k| h[k] = ''; h}
47
+ def initialize(attrs={})
48
+ super(EMPTY_ATTRIBUTES.merge(attrs))
34
49
  end
35
- def self.form_partial_path
36
- Pathname.new(__FILE__).dirname.join('..', '..', 'app', 'views', 'application', '_payment_method_form.html.erb')
50
+
51
+ # Prepare a new PaymentMethod for use with a transparent redirect form
52
+ def self.for_transparent_redirect(params)
53
+ if params[:payment_method_token].blank?
54
+ Samurai::PaymentMethod.new(params)
55
+ else
56
+ Samurai::PaymentMethod.find(params[:payment_method_token]).tap do |pm|
57
+ pm.card_number = "************#{pm.last_four_digits}"
58
+ pm.cvv = "***"
59
+ pm.errors[:base] << 'The card number or CVV are not valid.' if !pm.is_sensitive_data_valid
60
+ end
61
+ end
37
62
  end
38
63
 
39
64
  end
@@ -0,0 +1,7 @@
1
+ module Samurai
2
+ module Rails
3
+ end
4
+ end
5
+
6
+ require 'samurai/rails/views'
7
+ require 'samurai/rails/helpers'
@@ -0,0 +1,28 @@
1
+ require 'pathname'
2
+
3
+ module Samurai::Rails
4
+ module Helpers
5
+
6
+ def setup_for_transparent_redirect(params)
7
+ @transaction = Samurai::Transaction.find params[:reference_id] unless params[:reference_id].blank?
8
+ @payment_method = Samurai::PaymentMethod.for_transparent_redirect(params)
9
+ end
10
+
11
+ def load_and_verify_payment_method(params)
12
+ if params[:payment_method_token].blank?
13
+ @payment_method = Samurai::PaymentMethod.new :is_sensitive_data_valid=>false
14
+ else
15
+ @payment_method = Samurai::PaymentMethod.find params[:payment_method_token]
16
+ @payment_method = nil if @payment_method && !@payment_method.is_sensitive_data_valid?
17
+ end
18
+ @payment_method
19
+ end
20
+
21
+ def payment_method_params
22
+ { :payment_method_token => params[:payment_method_token] }.tap do |_params|
23
+ _params[:reference_id] = @transaction.reference_id if @transaction
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ require 'pathname'
2
+
3
+ module Samurai::Rails
4
+ class Views
5
+ class << self
6
+
7
+ PARTIALS = [ 'payment_method_form', 'transaction_form', 'errors', 'transaction' ]
8
+
9
+ PARTIALS.each do |partial|
10
+ define_method partial do |attrs|
11
+ attrs ||= {}
12
+ {:file=>send("#{partial}_file"), :locals=>attrs}
13
+ end
14
+ define_method "#{partial}_file" do
15
+ Pathname.new(__FILE__).dirname.join('../../..', 'app', 'views', 'application', "_#{partial}.html.erb").to_s
16
+ end
17
+ define_method "#{partial}_html" do
18
+ File.read(send partial)
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -27,6 +27,21 @@ class Samurai::Transaction < Samurai::Base
27
27
  execute(:credit, {:amount => amount || self.amount}.reverse_merge(options))
28
28
  end
29
29
 
30
+ # Reverse this transaction. First, tries a void.
31
+ # If a void is unsuccessful, (because the transaction has already settled) perform a credit for the full amount.
32
+ def reverse(options = {})
33
+ transaction = void(options)
34
+ return transaction if transaction.processor_response.success
35
+ return credit(nil, options)
36
+ end
37
+
38
+ def success?
39
+ respond_to?(:processor_response) && processor_response.success
40
+ end
41
+ def failed?
42
+ !success?
43
+ end
44
+
30
45
  private
31
46
 
32
47
  def execute(action, options = {})
@@ -62,6 +77,17 @@ class Samurai::Transaction < Samurai::Base
62
77
  to_xml(:skip_instruct => true, :root => 'transaction', :dasherize => false)
63
78
  end
64
79
 
80
+ # Initialize the known attributes from the schema as empty strings, so that they can be accessed via method-missing
81
+ KNOWN_ATTRIBUTES = [
82
+ :amount, :type, :payment_method_token, :currency_code,
83
+ :descriptor, :custom, :customer_reference, :billing_reference
84
+ ]
85
+ EMPTY_ATTRIBUTES = KNOWN_ATTRIBUTES.inject({}) {|h, k| h[k] = ''; h}
86
+ def initialize(attrs={})
87
+ super(EMPTY_ATTRIBUTES.merge(attrs))
88
+ end
89
+
90
+
65
91
  require 'pathname'
66
92
  def self.form_html
67
93
  File.read(form_partial_path)
@@ -1,3 +1,3 @@
1
1
  module Samurai
2
- VERSION = "0.2.4".freeze
2
+ VERSION = "0.2.5".freeze
3
3
  end
@@ -1,10 +1,10 @@
1
- require 'test/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "processing authorizations" do
4
4
 
5
5
  before :each do
6
- register_transaction_response(:type => 'authorize')
7
- @authorization = Samurai::Processor.authorize(PAYMENT_METHOD_TOKEN, @@seed)
6
+ payment_method_token = PAYMENT_METHOD_TOKENS[:success]
7
+ @authorization = Samurai::Processor.authorize(payment_method_token, @@seed)
8
8
  end
9
9
 
10
10
  it "should create a new authorization transaction" do
@@ -12,39 +12,33 @@ describe "processing authorizations" do
12
12
  end
13
13
 
14
14
  it "should find the authorization" do
15
- register_transaction_response(:method => :get, :path => "transactions/#{@authorization.reference_id}", :type => 'authorize')
16
15
  transaction = Samurai::Transaction.find(@authorization.reference_id)
17
16
  transaction.reference_id.intern.should be_equal(@authorization.reference_id.intern)
18
17
  end
19
18
 
20
19
  it "should successfully capture" do
21
- register_transaction_response(:method => :post, :path => "transactions/#{@authorization.id}/capture", :type => 'capture')
22
20
  capture = @authorization.capture(@@seed)
23
21
  capture.processor_response.success.should be_true
24
22
  end
25
23
 
26
24
  it "should capture an authorization without specifying an amount" do
27
- register_transaction_response(:method => :post, :path => "transactions/#{@authorization.id}/capture", :type => 'capture', :amount => @@seed)
28
25
  capture = @authorization.capture
29
26
  capture.amount.intern.should be_equal "#{@@seed}".intern
30
27
  capture.processor_response.success.should be_true
31
28
  end
32
29
 
33
30
  it "should partially capture an authorization" do
34
- register_transaction_response(:method => :post, :path => "transactions/#{@authorization.id}/capture", :type => 'capture', :amount => @@seed - 1.0)
35
31
  capture = @authorization.capture(@@seed - 1.0)
36
32
  capture.amount.intern.should be_equal "#{@@seed - 1.0}".intern
37
33
  capture.processor_response.success.should be_true
38
34
  end
39
35
 
40
36
  it "should void an authorization" do
41
- register_transaction_response(:method => :post, :path => "transactions/#{@authorization.id}/void", :type => 'void', :amount => @@seed)
42
37
  void = @authorization.void
43
38
  void.processor_response.success.should be_true
44
39
  end
45
40
 
46
41
  it "should credit an authorization for the full amount by default" do
47
- register_transaction_response(:method => :post, :path => "transactions/#{@authorization.id}/credit", :type => 'credit', :amount => @@seed, :success => 'false')
48
42
  credit = @authorization.credit
49
43
  credit.amount.intern.should be_equal "#{@@seed}".intern
50
44
  pending "the response is not successful since the authorization hasn't settled" do
@@ -53,7 +47,6 @@ describe "processing authorizations" do
53
47
  end
54
48
 
55
49
  it "should partially credit an authorization" do
56
- register_transaction_response(:method => :post, :path => "transactions/#{@authorization.id}/credit", :type => 'credit', :amount => @@seed - 1.0, :success => 'false')
57
50
  credit = @authorization.credit(@@seed - 1.0)
58
51
  credit.amount.intern.should be_equal "#{@@seed - 1.0}".intern
59
52
  pending "the response is not successful since the authorization hasn't settled" do
@@ -0,0 +1,207 @@
1
+ require 'spec_helper'
2
+
3
+ describe "generate documentation" do
4
+ include TransparentRedirectHelper
5
+ include ResponseLoggerHelper
6
+
7
+ before(:all) do
8
+ @logger = ResponseLogger.new(File.open('response_log.html', 'w'))
9
+ end
10
+ after(:all) do
11
+ @logger.close!
12
+ end
13
+
14
+ before do
15
+ @logger.begin_section example.full_description.sub(/generate documentation/, '').titleize
16
+
17
+ @params = {
18
+ 'redirect_url' => 'http://test.host',
19
+ 'merchant_key' => Samurai.merchant_key,
20
+ 'custom' => 'custom',
21
+ 'credit_card[first_name]' => 'FirstName',
22
+ 'credit_card[last_name]' => 'LastName',
23
+ 'credit_card[address_1]' => '123 Main St',
24
+ 'credit_card[address_2]' => '',
25
+ 'credit_card[city]' => 'Chicago',
26
+ 'credit_card[state]' => 'IL',
27
+ 'credit_card[zip]' => '60610',
28
+ 'credit_card[card_number]' => '4222222222222',
29
+ 'credit_card[cvv]' => '123',
30
+ 'credit_card[expiry_month]' => '05',
31
+ 'credit_card[expiry_year]' => '2014',
32
+ }
33
+
34
+ Samurai::Base.instance_eval do
35
+ def connection(refresh = false)
36
+ if defined?(@connection) || superclass == ActiveResource::Base
37
+ @connection ||= begin
38
+ c = HttpProxyConnection.new(site, format)
39
+ c.proxy = proxy if proxy
40
+ c.user = user if user
41
+ c.password = password if password
42
+ c.auth_type = auth_type if auth_type
43
+ c.timeout = timeout if timeout
44
+ c.ssl_options = ssl_options if ssl_options
45
+ c
46
+ end
47
+ else
48
+ superclass.connection
49
+ end
50
+ end
51
+ end
52
+ Samurai::Base.connection
53
+ end
54
+
55
+ after do
56
+ @logger.end_section
57
+ end
58
+
59
+ describe 'with an invalid payment method' do
60
+ it 'should not create the payment method with missing card_number' do
61
+ @params.delete 'credit_card[card_number]'
62
+ data = create_payment_method(@params)
63
+ log_request_response! data[:request], data[:response]
64
+ data[:payment_method_token].should be_nil
65
+ end
66
+
67
+ it 'should create the payment method with invalid card_number format' do
68
+ @params['credit_card[card_number]'] = '12345'
69
+ data = create_payment_method(@params)
70
+ log_request_response! data[:request], data[:response]
71
+ data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
72
+ Samurai::PaymentMethod.find data[:payment_method_token]
73
+ log_http!
74
+ end
75
+
76
+ it 'should create the payment method with blank cvv' do
77
+ @params['credit_card[cvv]'] = ''
78
+ data = create_payment_method(@params)
79
+ log_request_response! data[:request], data[:response]
80
+ data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
81
+ Samurai::PaymentMethod.find data[:payment_method_token]
82
+ log_http!
83
+ end
84
+ end
85
+
86
+ describe 'with a valid payment method' do
87
+ before do
88
+ @amount = '1.00' # response code: card number is declined
89
+ @data = create_payment_method(@params)
90
+ end
91
+ it 'should create the payment method' do
92
+ log_request_response! @data[:request], @data[:response]
93
+ @data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
94
+ Samurai::PaymentMethod.find @data[:payment_method_token]
95
+ log_http!
96
+ end
97
+ it 'should create a valid transaction' do
98
+ purchase = Samurai::Processor.purchase(@data[:payment_method_token], @amount)
99
+ log_http!
100
+ purchase.processor_response.success.should be_true
101
+ end
102
+ it 'should create an invalid transaction' do
103
+ lambda do
104
+ Samurai::Processor.purchase(@data[:payment_method_token], '')
105
+ end.should raise_error(ActiveResource::ServerError)
106
+ log_http!
107
+ end
108
+ end
109
+
110
+ describe 'with a declined card' do
111
+ before do
112
+ @amount = '3.00' # response code: card number is declined
113
+ @data = create_payment_method(@params)
114
+ end
115
+ it 'should create the payment method' do
116
+ log_request_response! @data[:request], @data[:response]
117
+ @data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
118
+ Samurai::PaymentMethod.find @data[:payment_method_token]
119
+ log_http!
120
+ end
121
+ it 'should create a valid transaction' do
122
+ purchase = Samurai::Processor.purchase(@data[:payment_method_token], @amount)
123
+ log_http!
124
+ purchase.processor_response.success.should be_false
125
+ end
126
+ it 'should create an invalid transaction' do
127
+ lambda do
128
+ Samurai::Processor.purchase(@data[:payment_method_token], '')
129
+ end.should raise_error(ActiveResource::ServerError)
130
+ log_http!
131
+ end
132
+ end
133
+
134
+ describe 'with an expired card' do
135
+ before do
136
+ @amount = '8.00' # response code: card is expired
137
+ @data = create_payment_method(@params)
138
+ end
139
+ it 'should create the payment method' do
140
+ log_request_response! @data[:request], @data[:response]
141
+ @data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
142
+ Samurai::PaymentMethod.find @data[:payment_method_token]
143
+ log_http!
144
+ end
145
+ it 'should create a valid transaction' do
146
+ purchase = Samurai::Processor.purchase(@data[:payment_method_token], @amount)
147
+ log_http!
148
+ purchase.processor_response.success.should be_false
149
+ end
150
+ it 'should create an invalid transaction' do
151
+ lambda do
152
+ Samurai::Processor.purchase(@data[:payment_method_token], '')
153
+ end.should raise_error(ActiveResource::ServerError)
154
+ log_http!
155
+ end
156
+ end
157
+
158
+ describe 'with a card with incorrect cvv' do
159
+ before do
160
+ @amount = '6.00' # response code: card number is invalid
161
+ @data = create_payment_method(@params)
162
+ end
163
+ it 'should create the payment method' do
164
+ log_request_response! @data[:request], @data[:response]
165
+ @data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
166
+ Samurai::PaymentMethod.find @data[:payment_method_token]
167
+ log_http!
168
+ end
169
+ it 'should create a valid transaction' do
170
+ purchase = Samurai::Processor.purchase(@data[:payment_method_token], @amount)
171
+ log_http!
172
+ purchase.processor_response.success.should be_false
173
+ end
174
+ it 'should create an invalid transaction' do
175
+ lambda do
176
+ Samurai::Processor.purchase(@data[:payment_method_token], '')
177
+ end.should raise_error(ActiveResource::ServerError)
178
+ log_http!
179
+ end
180
+ end
181
+
182
+ describe 'with an nonexistant card' do
183
+ before do
184
+ @params['credit_card[card_number]'] = '4222222222222'
185
+ @amount = '6.00' # response code: card number is invalid
186
+ @data = create_payment_method(@params)
187
+ end
188
+ it 'should create the payment method' do
189
+ log_request_response! @data[:request], @data[:response]
190
+ @data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
191
+ Samurai::PaymentMethod.find @data[:payment_method_token]
192
+ log_http!
193
+ end
194
+ it 'should create a valid transaction' do
195
+ purchase = Samurai::Processor.purchase(@data[:payment_method_token], @amount)
196
+ log_http!
197
+ purchase.processor_response.success.should be_false
198
+ end
199
+ it 'should create an invalid transaction' do
200
+ lambda do
201
+ Samurai::Processor.purchase(@data[:payment_method_token], '')
202
+ end.should raise_error(ActiveResource::ServerError)
203
+ log_http!
204
+ end
205
+ end
206
+
207
+ end
@@ -1,24 +1,23 @@
1
- require 'test/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "Processor actions" do
4
-
4
+ before :each do
5
+ @payment_method_token = PAYMENT_METHOD_TOKENS[:success]
6
+ end
7
+
5
8
  it "should return an empty processor" do
6
9
  processor = Samurai::Processor.the_processor
7
10
  processor.should_not be_nil
8
11
  end
9
12
 
10
13
  it "should create a new purchase" do
11
- register_transaction_response(:type => 'purchase')
12
-
13
- purchase = Samurai::Processor.purchase(PAYMENT_METHOD_TOKEN, @@seed)
14
+ purchase = Samurai::Processor.purchase(@payment_method_token, @@seed)
14
15
  purchase.processor_response.success.should be_true
15
16
  # FakeWeb.last_request
16
17
  end
17
18
 
18
19
  it "should create a new purchase with tracking data" do
19
- register_transaction_response(:type => 'purchase')
20
-
21
- purchase = Samurai::Processor.purchase(PAYMENT_METHOD_TOKEN, @@seed, {
20
+ purchase = Samurai::Processor.purchase(@payment_method_token, @@seed, {
22
21
  :descriptor => "A test purchase",
23
22
  :custom => "some optional custom data",
24
23
  :billing_reference => "ABC123",
@@ -29,8 +28,7 @@ describe "Processor actions" do
29
28
  end
30
29
 
31
30
  it "should create a non-new authorization" do
32
- register_transaction_response(:type => 'authorize')
33
- authorization = Samurai::Processor.authorize(PAYMENT_METHOD_TOKEN, @@seed)
31
+ authorization = Samurai::Processor.authorize(@payment_method_token, @@seed)
34
32
  authorization.processor_response.success.should be_true
35
33
  end
36
34
 
@@ -1,10 +1,10 @@
1
- require 'test/spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  describe "processing purchases" do
4
4
 
5
5
  before :each do
6
- register_transaction_response(:type => 'purchase')
7
- @purchase = Samurai::Processor.purchase(PAYMENT_METHOD_TOKEN, @@seed)
6
+ payment_method_token = PAYMENT_METHOD_TOKENS[:success]
7
+ @purchase = Samurai::Processor.purchase(payment_method_token, @@seed)
8
8
  end
9
9
 
10
10
  it "should process successfully" do
@@ -12,20 +12,28 @@ describe "processing purchases" do
12
12
  end
13
13
 
14
14
  it "should be able to void a recent purchase" do
15
- register_transaction_response(:method => :post, :path => "transactions/#{@purchase.id}/void", :type => 'void', :success => 'false')
16
15
  void = @purchase.void
17
16
  void.processor_response.success.should be_true
18
17
  end
19
18
 
20
19
  it "should not be able to credit a recent purchase" do
21
- register_transaction_response(:method => :post, :path => "transactions/#{@purchase.id}/credit", :type => 'void', :success => 'false')
22
20
  credit = @purchase.credit
23
21
  credit.processor_response.success.should be_false
24
22
  end
25
23
 
24
+ it "should be able to reverse a recent purchase" do
25
+ reverse = @purchase.reverse
26
+ reverse.processor_response.success.should be_true
27
+ end
28
+
29
+ it "should be able to reverse a settled purchase" do
30
+ pending "currently we cannot force settle a purchase, so can't test this properly"
31
+ reverse = @purchase.reverse
32
+ reverse.processor_response.success.should be_true
33
+ end
34
+
26
35
  it "should be able to credit a settled purchase" do
27
36
  pending "currently we cannot force settle a purchase, so can't test this properly" do
28
- register_transaction_response(:method => :post, :path => "transactions/#{@purchase.id}/credit", :type => 'void', :success => 'false')
29
37
  credit = @purchase.credit
30
38
  credit.processor_response.success.should be_true
31
39
  end
@@ -0,0 +1,34 @@
1
+ require 'rspec'
2
+ require 'ruby-debug'
3
+ require 'pp'
4
+ Debugger.start
5
+ Debugger.settings[:autoeval] = true
6
+ Debugger.settings[:autolist] = 5
7
+ Debugger.settings[:reload_source_on_change] = true
8
+
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ SITE = ENV['site'] || 'https://samurai.feefighters.com/v1/'
12
+ USE_MOCK = !ENV['site']
13
+
14
+ PAYMENT_METHOD_TOKENS = {
15
+ :success => 'b7c966452702282b32a4c65d'
16
+ }
17
+
18
+ RSpec.configure do |c|
19
+ c.before :all do
20
+ @@seed = rand(1000).to_f / 100.0
21
+ end
22
+ c.before :each do
23
+ @@seed += 1.0
24
+ end
25
+ end
26
+
27
+ require 'samurai'
28
+ Samurai.options = {
29
+ :site => SITE,
30
+ :merchant_key => ENV['merchant_key'] || 'f4b17359f267915e705fdcb6',
31
+ :merchant_password => ENV['merchant_password'] || 'd7bf19a8aa1051335b83b349',
32
+ :processor_token => ENV['processor_token'] || 'c5823b5f1616ed6c0891d167'
33
+ }
34
+
@@ -0,0 +1,40 @@
1
+ require 'active_resource/connection'
2
+
3
+ class HttpProxy
4
+ attr_accessor :request, :response
5
+
6
+ def initialize(http, options={})
7
+ @http = http
8
+ @response = nil
9
+ @request = {}
10
+ end
11
+
12
+ def method_missing(meth, *args, &block)
13
+ case meth
14
+ when :post, :put
15
+ @request = {
16
+ :method => meth,
17
+ :path => args[0],
18
+ :body => args[1],
19
+ :headers => args[2],
20
+ }
21
+ when :get, :delete
22
+ @request = {
23
+ :method => meth,
24
+ :path => args[0],
25
+ :headers => args[1],
26
+ }
27
+ end
28
+ @response = @http.send(meth, *args, &block)
29
+ end
30
+
31
+ def respond_to?(meth)
32
+ super || @http.respond_to?(meth)
33
+ end
34
+ end
35
+
36
+ class HttpProxyConnection < ActiveResource::Connection
37
+ def http
38
+ @http ||= HttpProxy.new(configure_http(new_http))
39
+ end
40
+ end
@@ -0,0 +1,71 @@
1
+ require "erb"
2
+
3
+ class ResponseLogger
4
+ include ERB::Util
5
+
6
+ def initialize(io)
7
+ @sections = []
8
+ @io = io
9
+ @io.puts '<div class="wrapper">'
10
+ end
11
+
12
+ def close!
13
+ @io.puts '</div>'
14
+ @sections.each do |section|
15
+ @io.puts "<a href='##{section[:id]}'>#{section[:name]}</a><br>"
16
+ end
17
+ end
18
+
19
+ def begin_section(name)
20
+ @sections << {:name=>name, :id=>h(name.parameterize)}
21
+ @io.puts "<article class='example span-8' id='#{h name.parameterize}'>"
22
+ @io.puts " <h3>#{h name}</h3>"
23
+ end
24
+
25
+ def end_section
26
+ @io.puts "</article>"
27
+ @io.puts "<hr>"
28
+ end
29
+
30
+ def log(request, response, options={})
31
+ @io.puts ' <div class="code http-request"><em class="lang">HTTP Request</em>'
32
+ @io.puts " <pre><strong>#{h request[:method].to_s.upcase} #{h request[:path]}</strong><br>"
33
+ @io.puts "Headers: #{h request[:headers].inject({}) {|h, (k,v)| h[k] = (v.is_a?(Array) ? v.first : v); h }}</pre>"
34
+ @io.puts ' </div>'
35
+ if request[:body]
36
+ @io.puts ' <div class="code xml">'
37
+ @io.puts ' <em class="lang">XML Payload</em>'
38
+ @io.puts " <pre class='prettyprint lang-xml'>#{h request[:body]}</pre>"
39
+ @io.puts ' </div>'
40
+ end
41
+ @io.puts ' <div class="code http-response">'
42
+ @io.puts " <em class='lang'>HTTP Response: #{h response.code}</em>"
43
+ @io.puts " <pre class='prettyprint lang-xml'><code>#{h response.body}</code></pre>"
44
+ @io.puts " </div>"
45
+ end
46
+
47
+ end
48
+
49
+ module ResponseLoggerHelper
50
+ def log_http! options={}
51
+ @logger.log Samurai::Base.connection.http.request,
52
+ Samurai::Base.connection.http.response,
53
+ options
54
+ end
55
+
56
+ def log_request_response! request, response, options={}
57
+ def request.[](v)
58
+ case v
59
+ when :method
60
+ return self.method
61
+ when :path
62
+ return self.path
63
+ when :headers
64
+ return self.to_hash
65
+ else
66
+ super
67
+ end
68
+ end
69
+ @logger.log request, response, options
70
+ end
71
+ end
@@ -0,0 +1,26 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module TransparentRedirectHelper
5
+
6
+ def create_payment_method(params = {}, options={})
7
+ url = Samurai.site + 'payment_methods'
8
+ url.sub! %r{https://}, "https://#{Samurai.merchant_key}:#{Samurai.merchant_password}@"
9
+
10
+ uri = URI.parse url
11
+ req = Net::HTTP::Post.new uri.path
12
+ req.set_form_data params
13
+ req.basic_auth uri.user, uri.password
14
+
15
+ res = Net::HTTP.new(uri.host, uri.port)
16
+ res.use_ssl = true
17
+
18
+ response = res.start {|http| http.request(req) }
19
+ {
20
+ :payment_method_token => response['Location'] && response['Location'].sub(%r{#{Regexp.escape params['redirect_url']}\?payment_method_token=}, ''),
21
+ :response => response,
22
+ :request => req,
23
+ }
24
+ end
25
+
26
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samurai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2011-08-09 00:00:00.000000000Z
13
+ date: 2011-08-18 00:00:00.000000000Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activeresource
17
- requirement: &70158124256520 !ruby/object:Gem::Requirement
17
+ requirement: &70227589695120 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ! '>='
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 2.2.2
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *70158124256520
25
+ version_requirements: *70227589695120
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bundler
28
- requirement: &70158124256060 !ruby/object:Gem::Requirement
28
+ requirement: &70227589694660 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ! '>='
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 1.0.0
34
34
  type: :development
35
35
  prerelease: false
36
- version_requirements: *70158124256060
36
+ version_requirements: *70227589694660
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: rspec
39
- requirement: &70158124255600 !ruby/object:Gem::Requirement
39
+ requirement: &70227589694200 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,10 +44,10 @@ dependencies:
44
44
  version: 2.6.0
45
45
  type: :development
46
46
  prerelease: false
47
- version_requirements: *70158124255600
47
+ version_requirements: *70227589694200
48
48
  - !ruby/object:Gem::Dependency
49
49
  name: fakeweb
50
- requirement: &70158124255220 !ruby/object:Gem::Requirement
50
+ requirement: &70227589693820 !ruby/object:Gem::Requirement
51
51
  none: false
52
52
  requirements:
53
53
  - - ! '>='
@@ -55,10 +55,10 @@ dependencies:
55
55
  version: '0'
56
56
  type: :development
57
57
  prerelease: false
58
- version_requirements: *70158124255220
58
+ version_requirements: *70227589693820
59
59
  - !ruby/object:Gem::Dependency
60
60
  name: ruby-debug19
61
- requirement: &70158124254720 !ruby/object:Gem::Requirement
61
+ requirement: &70227589693320 !ruby/object:Gem::Requirement
62
62
  none: false
63
63
  requirements:
64
64
  - - ! '>='
@@ -66,7 +66,7 @@ dependencies:
66
66
  version: '0'
67
67
  type: :development
68
68
  prerelease: false
69
- version_requirements: *70158124254720
69
+ version_requirements: *70227589693320
70
70
  description: If you are an online merchant and using samurai.feefighters.com, this
71
71
  gem will make your life easy. Integrate with the samurai.feefighters.com portal
72
72
  and process transaction.
@@ -83,6 +83,7 @@ files:
83
83
  - Gemfile.lock
84
84
  - README.markdown
85
85
  - Rakefile
86
+ - app/views/application/_errors.html.erb
86
87
  - app/views/application/_payment_method_form.html.erb
87
88
  - app/views/application/_transaction.html.erb
88
89
  - app/views/application/_transaction_form.html.erb
@@ -93,13 +94,20 @@ files:
93
94
  - lib/samurai/payment_method.rb
94
95
  - lib/samurai/processor.rb
95
96
  - lib/samurai/processor_response.rb
97
+ - lib/samurai/rails.rb
98
+ - lib/samurai/rails/helpers.rb
99
+ - lib/samurai/rails/views.rb
96
100
  - lib/samurai/transaction.rb
97
101
  - lib/samurai/version.rb
98
102
  - samurai.gemspec
99
- - test/spec/authorization_spec.rb
100
- - test/spec/processor_spec.rb
101
- - test/spec/purchase_spec.rb
102
- - test/spec_helper.rb
103
+ - spec/lib/authorization_spec.rb
104
+ - spec/lib/generate_docs_spec.rb
105
+ - spec/lib/processor_spec.rb
106
+ - spec/lib/purchase_spec.rb
107
+ - spec/spec_helper.rb
108
+ - spec/support/http_proxy.rb
109
+ - spec/support/response_logger.rb
110
+ - spec/support/transparent_redirect_helper.rb
103
111
  homepage: http://rubygems.org/gems/samurai
104
112
  licenses: []
105
113
  post_install_message:
data/test/spec_helper.rb DELETED
@@ -1,91 +0,0 @@
1
- require 'ruby-debug'
2
- Debugger.start
3
- Debugger.settings[:autoeval] = true
4
- Debugger.settings[:autolist] = 5
5
- Debugger.settings[:reload_source_on_change] = true
6
-
7
- SITE = ENV['site'] || 'http://localhost:3002/v1/'
8
- USE_MOCK = !ENV['site']
9
- PAYMENT_METHOD_TOKEN = ENV['payment_method_token'] || 'asdf'
10
-
11
- RSpec.configure do |c|
12
- c.before :all do
13
- @@seed = rand(1000).to_f / 100.0
14
- end
15
- c.before :each do
16
- @@seed += 1.0
17
- end
18
- end
19
-
20
- require 'fakeweb'
21
- FakeWeb.allow_net_connect = !USE_MOCK
22
-
23
- require 'samurai'
24
- Samurai.options = {
25
- :site => SITE,
26
- :merchant_key => ENV['merchant_key'] || 'e62c5a006cdd9908234193bc',
27
- :merchant_password => ENV['merchant_password'] || '18e87d97b3a44b56fe07497e4812f14555db69df9e6ca16f',
28
- :processor_token => ENV['processor_token'] || 'af762c3499f77c5f181650a7'
29
- }
30
-
31
- def register_transaction_response(options)
32
- return unless USE_MOCK
33
-
34
- options.symbolize_keys!
35
-
36
- method = options[:method] && options[:method].to_sym || :post
37
- type = options[:type]
38
- path = options[:path] || "processors/af762c3499f77c5f181650a7/#{type}"
39
- payment_method_token = options[:payment_method_token] || PAYMENT_METHOD_TOKEN
40
- amount = options[:amount] || 15.00
41
- success = options[:success].blank? ? true : options[:success]
42
-
43
- FakeWeb.register_uri(method,
44
- "http://e62c5a006cdd9908234193bc:18e87d97b3a44b56fe07497e4812f14555db69df9e6ca16f@localhost:3002/v1/#{path}.xml",
45
- :body => <<-EOF
46
- <transaction>
47
- <reference_id>3dcFjTC7LDjIjTY3nkKjBVZ8qkZ</reference_id>
48
- <transaction_token>53VFyQKYBmN9vKfA9mHCTs79L9a</transaction_token>
49
- <created_at type="datetime">2011-04-22T17:57:56Z</created_at>
50
- <descriptor>Custom descriptor here if your processor supports it.</descriptor>
51
- <custom>Any value you like.</custom>
52
- <transaction_type>#{type}</transaction_type>
53
- <amount>#{amount}</amount>
54
- <currency_code>USD</currency_code>
55
- <processor_token>af762c3499f77c5f181650a7</processor_token>
56
- <processor_response>
57
- <success type="boolean">#{success}</success>
58
- <messages type="array">
59
- <message class="error" context="processor.avs" key="country_not_supported" />
60
- <message class="error" context="input.cvv" key="too_short" />
61
- </messages>
62
- </processor_response>
63
- <payment_method>
64
- <payment_method_token>#{payment_method_token}</payment_method_token>
65
- <created_at type="datetime">2011-02-12T20:20:46Z</created_at>
66
- <updated_at type="datetime">2011-04-22T17:57:30Z</updated_at>
67
- <custom>Any value you want us to save with this payment method.</custom>
68
- <is_retained type="boolean">true</is_retained>
69
- <is_redacted type="boolean">false</is_redacted>
70
- <is_sensitive_data_valid type="boolean">true</is_sensitive_data_valid>
71
- <messages type="array">
72
- <message class="error" context="input.cvv" key="too_long" />
73
- <message class="error" context="input.card_number" key="failed_checksum" />
74
- </messages>
75
- <last_four_digits>1111</last_four_digits>
76
- <card_type>visa</card_type>
77
- <first_name>Bob</first_name>
78
- <last_name>Smith</last_name>
79
- <expiry_month type="integer">1</expiry_month>
80
- <expiry_year type="integer">2020</expiry_year>
81
- <address_1 nil="true"></address_1>
82
- <address_2 nil="true"></address_2>
83
- <city nil="true"></city>
84
- <state nil="true"></state>
85
- <zip nil="true"></zip>
86
- <country nil="true"></country>
87
- </payment_method>
88
- </transaction>
89
- EOF
90
- )
91
- end