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/Gemfile +2 -0
- data/Gemfile.lock +11 -2
- data/README.markdown +3 -163
- data/Rakefile +16 -0
- data/api_reference.markdown +303 -0
- data/lib/samurai.rb +16 -3
- data/lib/samurai/base.rb +7 -1
- data/lib/samurai/cacheable_by_token.rb +8 -2
- data/lib/samurai/message.rb +7 -1
- data/lib/samurai/payment_method.rb +14 -7
- data/lib/samurai/processor.rb +27 -16
- data/lib/samurai/processor_response.rb +5 -0
- data/lib/samurai/rails/helpers.rb +5 -0
- data/lib/samurai/rails/views.rb +14 -0
- data/lib/samurai/transaction.rb +14 -6
- data/lib/samurai/version.rb +1 -1
- data/samurai.gemspec +1 -1
- data/spec/lib/authorization_spec.rb +9 -9
- data/spec/lib/{generate_docs_spec.rb → generate_docs_spec.rb.bak} +3 -32
- data/spec/lib/processor_spec.rb +4 -4
- data/spec/lib/purchase_spec.rb +2 -2
- data/spec/spec_helper.rb +55 -11
- data/spec/support/response_logger.rb +2 -2
- data/spec/support/transaction_seed.rb +10 -0
- data/spec/support/transparent_redirect_helper.rb +19 -0
- metadata +68 -55
data/lib/samurai.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
1
|
-
#
|
2
|
-
#
|
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!
|
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
|
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
|
36
|
+
# Overrides the ActiveResource `save` method to update the current
|
31
37
|
# model in the cache
|
32
38
|
def save
|
33
39
|
super
|
data/lib/samurai/message.rb
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
|
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
|
11
|
+
def id
|
7
12
|
self.token
|
8
13
|
end
|
9
14
|
|
10
|
-
# Alias for
|
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
|
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
|
-
|
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
|
-
#
|
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)
|
data/lib/samurai/processor.rb
CHANGED
@@ -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
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
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
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
#
|
38
|
-
#
|
39
|
-
#
|
40
|
-
#
|
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
|
|
data/lib/samurai/rails/views.rb
CHANGED
@@ -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
|
data/lib/samurai/transaction.rb
CHANGED
@@ -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
|
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
|
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
|
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
|
data/lib/samurai/version.rb
CHANGED
data/samurai.gemspec
CHANGED
@@ -3,8 +3,8 @@ require 'spec_helper'
|
|
3
3
|
describe "processing authorizations" do
|
4
4
|
|
5
5
|
before :each do
|
6
|
-
payment_method_token =
|
7
|
-
@authorization = Samurai::Processor.authorize(payment_method_token,
|
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(
|
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 "#{
|
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(
|
32
|
-
capture.amount.intern.should be_equal "#{
|
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 "#{
|
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(
|
51
|
-
credit.amount.intern.should be_equal "#{
|
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
|
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
|