fat_zebra 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/FatZebra.gemspec +30 -0
- data/Gemfile +4 -0
- data/README.markdown +19 -0
- data/Rakefile +18 -0
- data/lib/fat_zebra/constants.rb +1 -0
- data/lib/fat_zebra/errors.rb +4 -0
- data/lib/fat_zebra/gateway.rb +215 -0
- data/lib/fat_zebra/models/base.rb +11 -0
- data/lib/fat_zebra/models/purchase.rb +7 -0
- data/lib/fat_zebra/models/refund.rb +11 -0
- data/lib/fat_zebra/models/response.rb +28 -0
- data/lib/fat_zebra/version.rb +3 -0
- data/lib/fat_zebra.rb +11 -0
- data/spec/gateway_spec.rb +36 -0
- data/spec/models/purchase_spec.rb +9 -0
- data/spec/models/refund_spec.rb +9 -0
- data/spec/models/response_spec.rb +5 -0
- data/spec/spec_helper.rb +8 -0
- data/vendor/cacert.pem +3390 -0
- metadata +144 -0
data/.gitignore
ADDED
data/FatZebra.gemspec
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "fat_zebra/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "fat_zebra"
|
7
|
+
s.version = FatZebra::VERSION
|
8
|
+
s.authors = ["Matthew Savage"]
|
9
|
+
s.email = ["matt@amasses.net"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Fat Zebra payments gem - integrate your ruby app with Fat Zebra}
|
12
|
+
s.description = %q{Provides integration with the Fat Zebra internet payment gateway (www.fatzebra.com.au), including purchase, refund, auth, capture and recurring billing functionality.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "fat_zebra"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
s.add_development_dependency "rspec"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "yard"
|
25
|
+
s.add_development_dependency "yard-tomdoc"
|
26
|
+
|
27
|
+
|
28
|
+
s.add_runtime_dependency "rest-client"
|
29
|
+
s.add_runtime_dependency "json"
|
30
|
+
end
|
data/Gemfile
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Ruby API Library for Fat Zebra
|
2
|
+
==============================
|
3
|
+
|
4
|
+
Further (better) readme content coming soon - I promise!
|
5
|
+
|
6
|
+
General Usage:
|
7
|
+
|
8
|
+
1. Add the gem to your project (i.e. Gemfile or otherwise)
|
9
|
+
2. Create a new FatZebra::Gateway object, and pass it your username, token, and true or false for using test mode.
|
10
|
+
4. Call the purchase() method on the gateway object.
|
11
|
+
5. Examine the result.
|
12
|
+
|
13
|
+
More details to follow (structure of request/response objects etc).
|
14
|
+
|
15
|
+
Documentation for the Fat Zebra API can be found at http://docs.fatzebra.com.au
|
16
|
+
|
17
|
+
As always, patches, pull requests, comments, issues etc welcome.
|
18
|
+
|
19
|
+
For support please visit https://www.fatzebra.com.au/help or email support@fatzebra.com.au
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
RSpec::Core::RakeTask.new('spec')
|
6
|
+
|
7
|
+
# If you want to make this the default task
|
8
|
+
task :default => :spec
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'yard'
|
12
|
+
desc "Run the YARDdoc task"
|
13
|
+
task :yard do
|
14
|
+
exec "yardoc --plugin yard-tomdoc"
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
puts "Please install the Yard gem to create docs"
|
18
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
GATEWAY_SERVER = "gateway.fatzebra.com.au"
|
@@ -0,0 +1,215 @@
|
|
1
|
+
module FatZebra
|
2
|
+
class Gateway
|
3
|
+
attr_accessor :username, :token, :gateway_server, :options
|
4
|
+
|
5
|
+
DEFAULT_OPTIONS = {:secure => true}
|
6
|
+
|
7
|
+
# Public: initializes a new gateway object
|
8
|
+
#
|
9
|
+
# username - merchants username
|
10
|
+
# token - merchants token for authentication
|
11
|
+
# gateway_service - the server for the gateway, defaults to 'gateway.fatzebra.com.au'
|
12
|
+
#
|
13
|
+
# Returns a new FatZebra::Gateway instance
|
14
|
+
def initialize(username, token, gateway_server = GATEWAY_SERVER, options = {})
|
15
|
+
self.username = username
|
16
|
+
self.token = token
|
17
|
+
self.gateway_server = gateway_server
|
18
|
+
self.options = DEFAULT_OPTIONS.merge(options)
|
19
|
+
|
20
|
+
require_field :username, :token, :gateway_server
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Performs a purchase transaction against the gateway
|
24
|
+
#
|
25
|
+
# amount - the amount as an integer e.g. (1.00 * 100).to_i
|
26
|
+
# card_holder - the card holders name
|
27
|
+
# card_number - the customers credit card number
|
28
|
+
# card_expiry - the customers card expiry date (as Date or string [mm/yyyy])
|
29
|
+
# cvv - the credit card verification value (cvv, cav, csc etc)
|
30
|
+
# reference - a reference for the purchase
|
31
|
+
# customer_ip - the customers IP address (for fraud prevention)
|
32
|
+
#
|
33
|
+
# Returns a new FatZebra::Models::Purchase object
|
34
|
+
def purchase(amount, card_holder, card_number, card_expiry, cvv, reference, customer_ip)
|
35
|
+
params = {
|
36
|
+
:amount => amount,
|
37
|
+
:card_holder => card_holder,
|
38
|
+
:card_number => card_number,
|
39
|
+
:card_expiry => extract_date(card_expiry),
|
40
|
+
:cvv => cvv,
|
41
|
+
:reference => reference,
|
42
|
+
:customer_ip => customer_ip
|
43
|
+
}
|
44
|
+
|
45
|
+
response = make_request(:post, "purchases", params)
|
46
|
+
FatZebra::Models::Response.new(response)
|
47
|
+
end
|
48
|
+
|
49
|
+
def purchases(id = nil)
|
50
|
+
if id.nil?
|
51
|
+
response = make_request(:get, "purchases")
|
52
|
+
# handle list response
|
53
|
+
else
|
54
|
+
response = make_request(:get, "purchases/#{id}.json")
|
55
|
+
puts response.inspect
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Performs an authorization transaction against the gateway
|
60
|
+
# Note: Successful transactions must then be captured for funds to settle.
|
61
|
+
#
|
62
|
+
# amount - the amount as an integer e.g. (1.00 * 100).to_i
|
63
|
+
# card_number - the customers credit card number
|
64
|
+
# card_expiry - the customers card expiry date
|
65
|
+
# cvv - the credit card verification value (cvv, cav, csc etc)
|
66
|
+
# reference - a reference for the purchase
|
67
|
+
# customer_ip - the customers IP address (for fraud prevention)
|
68
|
+
#
|
69
|
+
# Returns a new FatZebra::Models::Purchase object
|
70
|
+
def authorize(amount, card_number, card_expiry, cvv, reference, customer_ip)
|
71
|
+
raise "Sorry we haven't compelted this functionality yet."
|
72
|
+
end
|
73
|
+
|
74
|
+
# Public: Captures a pre-authorized transaction
|
75
|
+
#
|
76
|
+
# transaction_id - the authorization ID
|
77
|
+
# amount - the amount to capture, as an integer
|
78
|
+
#
|
79
|
+
# Returns a new FatZebra::Models::Purchase object
|
80
|
+
def capture(transaction_id, amount)
|
81
|
+
raise "Sorry we haven't compelted this functionality yet."
|
82
|
+
end
|
83
|
+
|
84
|
+
# Public: Refunds a transaction
|
85
|
+
#
|
86
|
+
# transaction_id - the ID of the original transaction to be refunded
|
87
|
+
# amount - the amount to be refunded, as an integer
|
88
|
+
# reference - the reference for the refund
|
89
|
+
#
|
90
|
+
# Returns a new FatZebra::Models::Refund object
|
91
|
+
def refund(transaction_id, amount, reference)
|
92
|
+
raise "Sorry we haven't compelted this functionality yet."
|
93
|
+
end
|
94
|
+
|
95
|
+
# Public: Pings the Fat Zebra service
|
96
|
+
#
|
97
|
+
# nonce - the data to be echoed back
|
98
|
+
#
|
99
|
+
# Returns true if reply is valid, false if times out or otherwise
|
100
|
+
def ping(nonce = SecureRandom.hex)
|
101
|
+
begin
|
102
|
+
response = RestClient.get(build_url("ping") + "?echo=#{nonce}")
|
103
|
+
response = JSON.parse(response)
|
104
|
+
|
105
|
+
response["echo"] == nonce
|
106
|
+
rescue => e
|
107
|
+
return false
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
private
|
113
|
+
# Private: Extracts the date value from a Date/DateTime value
|
114
|
+
#
|
115
|
+
# value - the string or date value to extract the value from
|
116
|
+
#
|
117
|
+
# Returns date string as MM/YYYY
|
118
|
+
def extract_date(value)
|
119
|
+
if value.is_a?(String)
|
120
|
+
return value
|
121
|
+
elsif value.respond_to?(:strftime)
|
122
|
+
return value.strftime("%m/%Y")
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Private: Verifies a require field is present and has a value
|
127
|
+
#
|
128
|
+
# fields - array of fields required to be present
|
129
|
+
#
|
130
|
+
# Returns nil
|
131
|
+
# Raises InvalidArgumentError if field is missing
|
132
|
+
def require_field(*fields)
|
133
|
+
fields.each do |field|
|
134
|
+
val = self.send(field)
|
135
|
+
raise InvalidArgumentError, "Parameter #{field} is required", caller if (val.nil? || val.to_s.length == 0)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Public: Builds the URL for the request
|
140
|
+
# If data is provided it will append as name/value pairs for a get request
|
141
|
+
#
|
142
|
+
# resource - the resource to append to the URL (e.g. purchases for https://something.com/purchases)
|
143
|
+
# data (optional) - a hash of the data name value pairs - if nil it will be ignored
|
144
|
+
#
|
145
|
+
# Returns the URL as a string
|
146
|
+
def build_url(resource, data = nil)
|
147
|
+
proto = case options[:secure]
|
148
|
+
when true
|
149
|
+
"https://"
|
150
|
+
when false
|
151
|
+
"http://"
|
152
|
+
end
|
153
|
+
url = "#{proto}#{self.gateway_server}/#{resource}"
|
154
|
+
unless data.nil?
|
155
|
+
url = url + "?"
|
156
|
+
data.each do |key, value|
|
157
|
+
url += "#{key}=#{value}" # TODO: URL Encode this
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
url
|
162
|
+
end
|
163
|
+
|
164
|
+
# Public: Builds a new RestClient resource object
|
165
|
+
#
|
166
|
+
# uri - the full URI for the request
|
167
|
+
# method - the method for the request - either :post or :get
|
168
|
+
# data - the data for the request - only required for :get methods
|
169
|
+
#
|
170
|
+
# Returns a new RestClient resource
|
171
|
+
def get_resource(uri, method = :post, data = nil)
|
172
|
+
url = build_url(uri, (method == :get ? data : nil))
|
173
|
+
ssl_options = options[:secure] ? {
|
174
|
+
:ssl_ca_file => File.expand_path(File.dirname(__FILE__) + "/../../vendor/cacert.pem"),
|
175
|
+
:verify_ssl => OpenSSL::SSL::VERIFY_PEER
|
176
|
+
} : {}
|
177
|
+
|
178
|
+
opts = {:user => self.username, :password => self.token}
|
179
|
+
RestClient::Resource.new(build_url(uri), opts.merge(ssl_options))
|
180
|
+
end
|
181
|
+
|
182
|
+
# Public: Performs the HTTP(s) request and returns a response object, handing errors etc
|
183
|
+
#
|
184
|
+
# method - the request method (:post or :get)
|
185
|
+
# resource - the resource for the request
|
186
|
+
# data - a hash of the data for the request
|
187
|
+
#
|
188
|
+
# Returns hash of response data
|
189
|
+
def make_request(method, resource, data = nil)
|
190
|
+
resource = get_resource(resource, method, data)
|
191
|
+
|
192
|
+
payload = (method == :post) ? data.to_json : {}
|
193
|
+
|
194
|
+
resource.send(method, payload) do |response, request, result, &block|
|
195
|
+
case response.code
|
196
|
+
when 201
|
197
|
+
JSON.parse(response)
|
198
|
+
when 200
|
199
|
+
JSON.parse(response)
|
200
|
+
when 400
|
201
|
+
raise RequestError, "Bad Data"
|
202
|
+
when 401
|
203
|
+
raise RequestError, "Unauthorized, please check your username and token"
|
204
|
+
when 404
|
205
|
+
raise RequestError, "Requested URL not found"
|
206
|
+
when 500
|
207
|
+
raise RequestError, "Server Error, please check https://www.fatzebra.com.au"
|
208
|
+
when 501
|
209
|
+
raise RequestError, "Problem processing your request - please check your data"
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module FatZebra
|
2
|
+
module Models
|
3
|
+
class Refund < Base
|
4
|
+
attr_accessor :amonut, :reference, :transaction_id, :original_transaction_id
|
5
|
+
|
6
|
+
def original_transaction
|
7
|
+
@original_transaction ||= Purchase.find(self.original_transaction_id)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module FatZebra
|
2
|
+
module Models
|
3
|
+
class Response
|
4
|
+
attr_accessor :successful, :result, :errors, :test
|
5
|
+
def initialize(response, type = :purchase)
|
6
|
+
self.test = response["test"]
|
7
|
+
self.successful = response["successful"]
|
8
|
+
self.errors = response["errors"]
|
9
|
+
case type
|
10
|
+
when :purchase
|
11
|
+
self.result = Purchase.new(response["response"])
|
12
|
+
alias purchase result
|
13
|
+
when :refund
|
14
|
+
self.result = Refund.new(response["response"])
|
15
|
+
alias refund result
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def successful?
|
20
|
+
self.successful
|
21
|
+
end
|
22
|
+
|
23
|
+
def test?
|
24
|
+
self.test
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/fat_zebra.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
require 'json'
|
3
|
+
require 'rest_client'
|
4
|
+
require 'fat_zebra/errors'
|
5
|
+
require "fat_zebra/version"
|
6
|
+
require 'fat_zebra/constants'
|
7
|
+
require 'fat_zebra/gateway'
|
8
|
+
require 'fat_zebra/models/base'
|
9
|
+
require 'fat_zebra/models/purchase'
|
10
|
+
require 'fat_zebra/models/refund'
|
11
|
+
require 'fat_zebra/models/response'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
TEST_USER = "TEST"
|
4
|
+
TEST_TOKEN = "TEST"
|
5
|
+
TEST_LOCAL = true
|
6
|
+
|
7
|
+
describe FatZebra::Gateway do
|
8
|
+
before :each do
|
9
|
+
# Setup the gateway for testing
|
10
|
+
server = TEST_LOCAL == true ? "fatapi.dev" : "gateway.fatzebra.com.au"
|
11
|
+
@gw = FatZebra::Gateway.new(TEST_USER, TEST_TOKEN, server, {:secure => !TEST_LOCAL})
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should require username and token are provided" do
|
15
|
+
lambda { FatZebra::Gateway.new("test", nil) }.should raise_exception(FatZebra::InvalidArgumentError)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should require that the gateway_server arg is not nil or empty" do
|
19
|
+
lambda { FatZebra::Gateway.new("test", "test", "") }.should raise_exception(FatZebra::InvalidArgumentError)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should load a valid instance of the gateway" do
|
23
|
+
@gw.ping.should be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should perform a purchase" do
|
27
|
+
result = @gw.purchase(10000, "Matthew Savage", "5123456789012346", "05/2013", 123, "TEST#{rand}", "1.2.3.4")
|
28
|
+
result.should be_successful
|
29
|
+
result.errors.should be_empty
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should fetch a purchase" do
|
33
|
+
result = @gw.purchase(10000, "Matthew Savage", "5123456789012346", "05/2013", 123, "TES#{rand}T", "1.2.3.4")
|
34
|
+
@gw.purchases(result.purchase.id)
|
35
|
+
end
|
36
|
+
end
|