fat_zebra 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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