samurai 0.2.18 → 0.2.19

Sign up to get free protection for your applications and to get access to all the features.
data/lib/samurai.rb CHANGED
@@ -1,6 +1,19 @@
1
- # Author:: Graeme Rouse
2
- # Copyright:: Copyright (c) 2011 Arizona Bay, LLC
1
+ # Samurai Gem
2
+ # -----------------
3
3
 
4
+ # Core Samurai Module
5
+ # Contains accessors for the important Samurai configuration settings
6
+ #
7
+ # To configure the Samurai gem:
8
+ #
9
+ # ```ruby
10
+ # require 'samurai'
11
+ # Samurai.options = {
12
+ # :merchant_key => 'a1ebafb6da5238fb8a3ac9f6',
13
+ # :merchant_password => 'ae1aa640f6b735c4730fbb56',
14
+ # :processor_token => '69ac9c704329bb067d427bf0'
15
+ # }
16
+ # ```
4
17
  module Samurai
5
18
  SITE = 'https://api.samurai.feefighters.com/v1/'
6
19
  DEFAULT_OPTIONS = {:site => SITE}
@@ -33,6 +46,7 @@ module Samurai
33
46
 
34
47
  end
35
48
 
49
+ # Require each of the samurai components
36
50
  require 'samurai/cacheable_by_token'
37
51
  require 'samurai/base'
38
52
  require 'samurai/processor'
@@ -40,7 +54,6 @@ require 'samurai/payment_method'
40
54
  require 'samurai/transaction'
41
55
  require 'samurai/message'
42
56
  require 'samurai/processor_response'
43
-
44
57
  require 'samurai/rails'
45
58
 
46
59
 
data/lib/samurai/base.rb CHANGED
@@ -3,10 +3,16 @@ begin
3
3
  rescue LoadError
4
4
  require 'activeresource' # for older versions of activeresource
5
5
  end
6
+
7
+ # Samurai::Base
8
+ # -----------------
9
+
10
+ # Base class that all Samurai ActiveResource models inherit from
11
+ # Provides some common error-handling functionality, as well as the AR site settings
6
12
  class Samurai::Base < ActiveResource::Base
7
13
  self.format = ActiveResource::Formats::XmlFormat
8
14
 
9
- def self.setup_site! # :nodoc:
15
+ def self.setup_site!
10
16
  self.site = Samurai.site
11
17
  self.user = Samurai.merchant_key
12
18
  self.password = Samurai.merchant_password
@@ -1,7 +1,13 @@
1
+ # Samurai::CacheableByToken
2
+ # -----------------
3
+
4
+ # Module for enabling caching of ActiveResource classes by token
5
+ # Essentially functions as a simple Identity Map
1
6
  module Samurai::CacheableByToken
2
7
 
3
8
  # The default cache stores the values for the duration of the request
4
9
  # Different caching strategies can be employed to keep the data around longer:
10
+ #
5
11
  # * class variables
6
12
  # * Rails.cache
7
13
  # * memcached
@@ -13,7 +19,7 @@ module Samurai::CacheableByToken
13
19
  end
14
20
 
15
21
  module ClassExtensions
16
- # Override the ActiveResource +find+ method to query the cache before hitting the provider.
22
+ # Override the ActiveResource `find` method to query the cache before hitting the provider.
17
23
  def find(*arguments)
18
24
  token = arguments.first
19
25
  if token.is_a?(String) && self.cache[token]
@@ -27,7 +33,7 @@ module Samurai::CacheableByToken
27
33
  end
28
34
  end
29
35
 
30
- # Overrides the ActiveResource +save+ method to update the current
36
+ # Overrides the ActiveResource `save` method to update the current
31
37
  # model in the cache
32
38
  def save
33
39
  super
@@ -1,4 +1,9 @@
1
- class Samurai::Message < Samurai::Base # :nodoc:
1
+ # Samurai::Message
2
+ # -----------------
3
+
4
+ # Simple class for serializing Samurai <message> responses
5
+ class Samurai::Message < Samurai::Base
6
+
2
7
  def to_xml(options = {})
3
8
  builder = options[:builder] || Builder::XmlMarkup.new(options)
4
9
  builder.tag!(:message) do
@@ -7,4 +12,5 @@ class Samurai::Message < Samurai::Base # :nodoc:
7
12
  end
8
13
  end
9
14
  end
15
+
10
16
  end
@@ -1,18 +1,24 @@
1
1
  require 'active_resource/version'
2
+
3
+ # Samurai::PaymentMethod
4
+ # -----------------
5
+
6
+ # Samurai credit card tokenization, including retaining & redacting Payment Methods
2
7
  class Samurai::PaymentMethod < Samurai::Base
3
8
 
4
9
  include Samurai::CacheableByToken
5
10
 
6
- def id # :nodoc:
11
+ def id
7
12
  self.token
8
13
  end
9
14
 
10
- # Alias for +payment_method_token+
15
+ # Alias for `payment_method_token`
11
16
  def token
12
17
  self.payment_method_token
18
+ # self.attributes["payment_method_token"]
13
19
  end
14
20
 
15
- # Retains the payment method on api.samurai.feefighters.com. Retain a payment method if
21
+ # Retains the payment method on `api.samurai.feefighters.com`. Retain a payment method if
16
22
  # it will not be used immediately.
17
23
  def retain
18
24
  self.post(:retain)
@@ -28,17 +34,18 @@ class Samurai::PaymentMethod < Samurai::Base
28
34
  @custom_data ||= self.custom && (JSON.parse(self.custom) rescue {}).symbolize_keys
29
35
  end
30
36
 
37
+ # Override base error processing with specific PaymentMethod behavior
38
+ # Examine the `<messages>` array, and add an error to the Errors object for each `<message>`
31
39
  def process_response_errors
32
40
  if respond_to?(:messages) && self.messages
33
41
  self.messages.each do |message|
34
- #if (message.respond_to?(:subclass) && message.subclass == 'error')
35
- self.errors.add message.context.gsub(/\./, ' '), message.key
36
- #end
42
+ self.errors.add message.context.gsub(/\./, ' '), message.key
37
43
  end
38
44
  end
39
45
  end
40
46
  protected :process_response_errors
41
47
 
48
+ # Setup the PaymentMethod schema for ActiveResource, so that new objects contain empty attributes
42
49
  KNOWN_ATTRIBUTES = [
43
50
  :first_name, :last_name, :address_1, :address_2, :city, :state, :zip,
44
51
  :card_number, :cvv, :expiry_month, :expiry_year
@@ -57,7 +64,7 @@ class Samurai::PaymentMethod < Samurai::Base
57
64
  end
58
65
  end
59
66
 
60
- # Prepare a new PaymentMethod for use with a transparent redirect form
67
+ # Convenience method for preparing a new PaymentMethod for use with a transparent redirect form
61
68
  def self.for_transparent_redirect(params)
62
69
  if params[:payment_method_token].blank?
63
70
  Samurai::PaymentMethod.new(params)
@@ -1,3 +1,8 @@
1
+ # Samurai::Processor
2
+ # -----------------
3
+
4
+ # This class represents a Samurai Processor connection
5
+ # It can be used to create purchase & authorize transactions
1
6
  class Samurai::Processor < Samurai::Base
2
7
 
3
8
  # Returns the default processor specified by Samurai.processor_token if you passed it into Samurai.setup_site.
@@ -17,34 +22,40 @@ class Samurai::Processor < Samurai::Base
17
22
 
18
23
  # Convenience method to authorize and capture a payment_method for a particular amount in one transaction.
19
24
  # Parameters:
20
- # +payment_method_token+:: token identifying the payment method to authorize
21
- # +amount+:: amount to authorize
22
- # options:: an optional has of additional values to pass in accepted values are:
23
- # *+descriptor+:: descriptor for the transaction
24
- # *+custom+:: custom data, this data does not get passed to the processor, it is stored within api.samurai.feefighters.com only
25
- # *+customer_reference+:: an identifier for the customer, this will appear in the processor if supported
26
- # *+billing_reference::+ an identifier for the purchase, this will appear in the processor if supported
25
+ #
26
+ # * `payment_method_token`: token identifying the payment method to authorize
27
+ # * `amount`: amount to authorize
28
+ # * `options`: an optional has of additional values to pass in accepted values are:
29
+ # * `descriptor`: descriptor for the transaction
30
+ # * `custom`: custom data, this data does not get passed to the processor, it is stored within `api.samurai.feefighters.com` only
31
+ # * `customer_reference`: an identifier for the customer, this will appear in the processor if supported
32
+ # * `billing_reference`: an identifier for the purchase, this will appear in the processor if supported
33
+ #
27
34
  # Returns a Samurai::Transaction containing the processor's response.
28
35
  def purchase(payment_method_token, amount, options = {})
29
36
  execute(:purchase, options.merge(:payment_method_token => payment_method_token, :amount => amount))
30
37
  end
31
38
 
32
- # Authorize a payment_method for a particular amount.
39
+ # Authorize a payment_method for a particular amount.
33
40
  # Parameters:
34
- # +payment_method_token+:: token identifying the payment method to authorize
35
- # +amount+:: amount to authorize
36
- # options:: an optional has of additional values to pass in accepted values are:
37
- # *+descriptor+:: descriptor for the transaction
38
- # *+custom+:: custom data, this data does not get passed to the processor, it is stored within api.samurai.feefighters.com only
39
- # *+customer_reference+:: an identifier for the customer, this will appear in the processor if supported
40
- # *+billing_reference::+ an identifier for the purchase, this will appear in the processor if supported
41
+ #
42
+ # * `payment_method_token`: token identifying the payment method to authorize
43
+ # * `amount`: amount to authorize
44
+ #
45
+ # * options: an optional has of additional values to pass in accepted values are:
46
+ # * `descriptor`: descriptor for the transaction
47
+ # * `custom`: custom data, this data does not get passed to the processor, it is stored within api.samurai.feefighters.com only
48
+ # * `customer_reference`: an identifier for the customer, this will appear in the processor if supported
49
+ # * `billing_reference`: an identifier for the purchase, this will appear in the processor if supported
50
+ #
41
51
  # Returns a Samurai::Transaction containing the processor's response.
42
52
  def authorize(payment_method_token, amount, options = {})
43
53
  execute(:authorize, options.merge(:payment_method_token => payment_method_token, :amount => amount))
44
54
  end
45
55
 
46
56
  private
47
-
57
+
58
+ # Make the actual ActiveResource POST request, process the response
48
59
  def execute(action, options = {})
49
60
  transaction = Samurai::Transaction.transaction_payload(options)
50
61
  begin
@@ -1,5 +1,10 @@
1
+ # Samurai::ProcessorResponse
2
+ # -----------------
3
+
4
+ # Simple class for serializing Samurai <processor_response> entities
1
5
  class Samurai::ProcessorResponse < Samurai::Base
2
6
 
7
+ # Helper method for accessing the AVS result code from the response messages
3
8
  def avs_result_code
4
9
  avs_result_code_message = self.messages.find {|m| m.context=='processor.avs_result_code' || m.context=='gateway.avs_result_code' }
5
10
  avs_result_code_message && avs_result_code_message.key
@@ -1,5 +1,10 @@
1
1
  require 'pathname'
2
2
 
3
+ # Samurai::Rails::Helpers
4
+ # -----------------
5
+
6
+ # Helper module containing useful methods that can be called from Rails controllers
7
+ # Useful for setting up objects properly for a Transparent Redirect, or displaying errors on payment forms
3
8
  module Samurai::Rails
4
9
  module Helpers
5
10
 
@@ -1,5 +1,19 @@
1
1
  require 'pathname'
2
2
 
3
+ # Samurai::Rails::Views
4
+ # -----------------
5
+
6
+ # Helper module containing methods designed to be called inside Rails views
7
+ # These methods render Samurai partials that ship with the gem,
8
+ # making it possible to create a payment form, and display transaction errors, in a single line of code
9
+ # eg:
10
+ #
11
+ # ```ruby
12
+ # <%= render Samurai::Rails::Views.errors %>
13
+ # <%= render Samurai::Rails::Views.payment_form
14
+ # :redirect_url => purchase_article_orders_url(@article),
15
+ # :sandbox => true %>
16
+ # ```
3
17
  module Samurai::Rails
4
18
  class Views
5
19
  class << self
@@ -1,14 +1,18 @@
1
+ # Samurai::Transaction
2
+ # -----------------
3
+
4
+ # This class represents a Samurai Transaction
5
+ # It can be used to query Transactions & capture/void/credit/reverse
1
6
  class Samurai::Transaction < Samurai::Base
2
-
3
7
  include Samurai::CacheableByToken
4
8
 
5
- # Alias for transaction_token
6
- def id # :nodoc:
9
+ # Alias for `transaction_token`
10
+ def id
7
11
  transaction_token
8
12
  end
9
13
  alias_method :token, :id
10
14
 
11
- # Captures an authorization. Optionally specify an +amount+ to do a partial capture of the initial
15
+ # Captures an authorization. Optionally specify an `amount` to do a partial capture of the initial
12
16
  # authorization. The default is to capture the full amount of the authorization.
13
17
  def capture(amount = nil, options = {})
14
18
  execute(:capture, {:amount => amount || self.amount}.reverse_merge(options))
@@ -21,7 +25,7 @@ class Samurai::Transaction < Samurai::Base
21
25
  end
22
26
 
23
27
  # Create a credit or refund against the original transaction.
24
- # Optionally accepts an +amount+ to credit, the default is to credit the full
28
+ # Optionally accepts an `amount` to credit, the default is to credit the full
25
29
  # value of the original amount
26
30
  def credit(amount = nil, options = {})
27
31
  execute(:credit, {:amount => amount || self.amount}.reverse_merge(options))
@@ -43,7 +47,8 @@ class Samurai::Transaction < Samurai::Base
43
47
  end
44
48
 
45
49
  private
46
-
50
+
51
+ # Make the actual ActiveResource POST request, process the response
47
52
  def execute(action, options = {})
48
53
  begin
49
54
  resp = post(action, {}, self.class.transaction_payload(options))
@@ -59,6 +64,8 @@ class Samurai::Transaction < Samurai::Base
59
64
  end
60
65
  end
61
66
 
67
+ # Override base error processing with specific Transaction behavior
68
+ # Examine the `<processor_response><messages>` array, and add an error to the Errors object for each `<message>`
62
69
  def process_response_errors
63
70
  if self.processor_response && self.processor_response.messages
64
71
  self.processor_response.messages.each do |message|
@@ -86,6 +93,7 @@ class Samurai::Transaction < Samurai::Base
86
93
  to_xml(:skip_instruct => true, :root => 'transaction', :dasherize => false)
87
94
  end
88
95
 
96
+ # Setup the Transaction schema for ActiveResource, so that new objects contain empty attributes
89
97
  KNOWN_ATTRIBUTES = [
90
98
  :amount, :type, :payment_method_token, :currency_code,
91
99
  :descriptor, :custom, :customer_reference, :billing_reference, :processor_response
@@ -1,3 +1,3 @@
1
1
  module Samurai
2
- VERSION = "0.2.18".freeze
2
+ VERSION = "0.2.19".freeze
3
3
  end
data/samurai.gemspec CHANGED
@@ -28,4 +28,4 @@ Gem::Specification.new do |s|
28
28
  s.files = `git ls-files`.split("\n")
29
29
  s.executables = `git ls-files`.split("\n").map{|f| f =~ /^bin\/(.*)/ ? $1 : nil}.compact
30
30
  s.require_path = 'lib'
31
- end
31
+ end
@@ -3,8 +3,8 @@ require 'spec_helper'
3
3
  describe "processing authorizations" do
4
4
 
5
5
  before :each do
6
- payment_method_token = PAYMENT_METHOD_TOKENS[:success]
7
- @authorization = Samurai::Processor.authorize(payment_method_token, @@seed)
6
+ payment_method_token = create_payment_method(default_payment_method_params)[:payment_method_token]
7
+ @authorization = Samurai::Processor.authorize(payment_method_token, 1.0, :billing_reference=>rand(1000))
8
8
  end
9
9
 
10
10
  it "should create a new authorization transaction" do
@@ -17,19 +17,19 @@ describe "processing authorizations" do
17
17
  end
18
18
 
19
19
  it "should successfully capture" do
20
- capture = @authorization.capture(@@seed)
20
+ capture = @authorization.capture(1.0)
21
21
  capture.processor_response.success.should be_true
22
22
  end
23
23
 
24
24
  it "should capture an authorization without specifying an amount" do
25
25
  capture = @authorization.capture
26
- capture.amount.intern.should be_equal "#{@@seed}".intern
26
+ capture.amount.intern.should be_equal "#{1.0}".intern
27
27
  capture.processor_response.success.should be_true
28
28
  end
29
29
 
30
30
  it "should partially capture an authorization" do
31
- capture = @authorization.capture(@@seed - 1.0)
32
- capture.amount.intern.should be_equal "#{@@seed - 1.0}".intern
31
+ capture = @authorization.capture(1.0 - BigDecimal('0.5'))
32
+ capture.amount.intern.should be_equal "#{1.0 - BigDecimal('0.5')}".intern
33
33
  capture.processor_response.success.should be_true
34
34
  end
35
35
 
@@ -40,15 +40,15 @@ describe "processing authorizations" do
40
40
 
41
41
  it "should credit an authorization for the full amount by default" do
42
42
  credit = @authorization.credit
43
- credit.amount.intern.should be_equal "#{@@seed}".intern
43
+ credit.amount.intern.should be_equal "#{1.0}".intern
44
44
  pending "the response is not successful since the authorization hasn't settled" do
45
45
  credit.processor_response.success.should be_true
46
46
  end
47
47
  end
48
48
 
49
49
  it "should partially credit an authorization" do
50
- credit = @authorization.credit(@@seed - 1.0)
51
- credit.amount.intern.should be_equal "#{@@seed - 1.0}".intern
50
+ credit = @authorization.credit(1.0 - BigDecimal('0.5'))
51
+ credit.amount.intern.should be_equal "#{1.0 - BigDecimal('0.5')}".intern
52
52
  pending "the response is not successful since the authorization hasn't settled" do
53
53
  credit.processor_response.success.should be_true
54
54
  end
@@ -1,7 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe "generate documentation" do
4
- include TransparentRedirectHelper
5
4
  include ResponseLoggerHelper
6
5
 
7
6
  before(:all) do
@@ -61,7 +60,9 @@ describe "generate documentation" do
61
60
  @params.delete 'credit_card[card_number]'
62
61
  data = create_payment_method(@params)
63
62
  log_request_response! data[:request], data[:response]
64
- data[:payment_method_token].should be_nil
63
+ data[:payment_method_token].should =~ /^[0-9a-z]{24}$/
64
+ Samurai::PaymentMethod.find data[:payment_method_token]
65
+ log_http!
65
66
  end
66
67
 
67
68
  it 'should create the payment method with invalid card_number format' do
@@ -99,12 +100,6 @@ describe "generate documentation" do
99
100
  log_http!
100
101
  purchase.processor_response.success.should be_true
101
102
  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
103
  end
109
104
 
110
105
  describe 'with a declined card' do
@@ -123,12 +118,6 @@ describe "generate documentation" do
123
118
  log_http!
124
119
  purchase.processor_response.success.should be_false
125
120
  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
121
  end
133
122
 
134
123
  describe 'with an expired card' do
@@ -147,12 +136,6 @@ describe "generate documentation" do
147
136
  log_http!
148
137
  purchase.processor_response.success.should be_false
149
138
  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
139
  end
157
140
 
158
141
  describe 'with a card with incorrect cvv' do
@@ -171,12 +154,6 @@ describe "generate documentation" do
171
154
  log_http!
172
155
  purchase.processor_response.success.should be_false
173
156
  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
157
  end
181
158
 
182
159
  describe 'with an nonexistant card' do
@@ -196,12 +173,6 @@ describe "generate documentation" do
196
173
  log_http!
197
174
  purchase.processor_response.success.should be_false
198
175
  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
176
  end
206
177
 
207
178
  end