fat_zebra 0.0.1
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/.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
|