samurai 0.2.18 → 0.2.19
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.
- 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
|