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 ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .yardoc
6
+ doc/
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
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in FatZebra.gemspec
4
+ gemspec
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,4 @@
1
+ module FatZebra
2
+ class InvalidArgumentError < StandardError; end
3
+ class RequestError < StandardError; end
4
+ end
@@ -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 Base
4
+ def initialize(attrs = {})
5
+ attrs.each do |key, val|
6
+ self.send("#{key}=", val) if self.respond_to?("#{key}=")
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ module FatZebra
2
+ module Models
3
+ class Purchase < Base
4
+ attr_accessor :id, :amount, :reference
5
+ end
6
+ end
7
+ 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
@@ -0,0 +1,3 @@
1
+ module FatZebra
2
+ VERSION = "0.0.1"
3
+ 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
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe FatZebra::Models::Purchase do
4
+ subject { FatZebra::Models::Purchase.new }
5
+
6
+ it "should be instance of base" do
7
+ # subject.should be_instance_of(FatZebra::Models::Base)
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'spec_helper'
2
+
3
+ describe FatZebra::Models::Refund do
4
+ subject { FatZebra::Models::Refund.new }
5
+
6
+ it "should be instance of base" do
7
+ # subject.should be_instance_of(FatZebra::Models::Base)
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe FatZebra::Models::Response do
4
+
5
+ end
@@ -0,0 +1,8 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+
4
+ require 'fat_zebra' # and any other gems you need
5
+
6
+ RSpec.configure do |config|
7
+
8
+ end