cardia 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,4 @@
1
+ == 0.0.1 2007-08-30
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
data/License.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2007 Marius Mårnes Mathiesen
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Manifest.txt ADDED
@@ -0,0 +1,28 @@
1
+ History.txt
2
+ License.txt
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ config/hoe.rb
7
+ config/requirements.rb
8
+ lib/cardia.rb
9
+ lib/cardia/credit_card_transaction.rb
10
+ lib/cardia/customer.rb
11
+ lib/cardia/merchant.rb
12
+ lib/cardia/order.rb
13
+ lib/cardia/transaction_status.rb
14
+ lib/cardia/version.rb
15
+ lib/credit_card.rb
16
+ lib/enum.rb
17
+ lib/validateable.rb
18
+ log/debug.log
19
+ script/destroy
20
+ script/generate
21
+ setup.rb
22
+ tasks/deployment.rake
23
+ tasks/environment.rake
24
+ tasks/website.rake
25
+ test/test_cardia.rb
26
+ test/test_credit_card_transaction.rb
27
+ test/test_helper.rb
28
+ test/test_transaction_status.rb
data/README.txt ADDED
@@ -0,0 +1,8 @@
1
+ = Cardia
2
+ Implements payments through Norwegian payment gateway Cardia's Cardia Service.
3
+
4
+ See Cardia::CreditCardTransaction for example usage
5
+
6
+ == Other sources of information
7
+ * Cardia's own documentation is available at http://www.cardia.no/Resource/Documentation/Services/Cardia.Services.Card.pdf
8
+ * SOAP interface description: https://secure.cardia.no/service/card/transaction/1.2/transaction.asmx
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
data/config/hoe.rb ADDED
@@ -0,0 +1,70 @@
1
+ require 'cardia/version'
2
+
3
+ AUTHOR = 'Marius Mårnes Mathiesen' # can also be an array of Authors
4
+ EMAIL = "marius.mathiesen@gmail.com"
5
+ DESCRIPTION = "provides credit card payments through Cardia"
6
+ GEM_NAME = 'cardia' # what ppl will type to install your gem
7
+ RUBYFORGE_PROJECT = 'cardia' # The unix name for your project
8
+ HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
9
+ DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
10
+
11
+ @config_file = "~/.rubyforge/user-config.yml"
12
+ @config = nil
13
+ RUBYFORGE_USERNAME = "zmalltalker"
14
+ def rubyforge_username
15
+ unless @config
16
+ begin
17
+ @config = YAML.load(File.read(File.expand_path(@config_file)))
18
+ rescue
19
+ puts <<-EOS
20
+ ERROR: No rubyforge config file found: #{@config_file}"
21
+ Run 'rubyforge setup' to prepare your env for access to Rubyforge
22
+ - See http://newgem.rubyforge.org/rubyforge.html for more details
23
+ EOS
24
+ exit
25
+ end
26
+ end
27
+ RUBYFORGE_USERNAME.replace @config["username"]
28
+ end
29
+
30
+
31
+ REV = nil
32
+ # UNCOMMENT IF REQUIRED:
33
+ # REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
34
+ VERS = Cardia::VERSION::STRING + (REV ? ".#{REV}" : "")
35
+ RDOC_OPTS = ['--quiet', '--title', 'cardia documentation',
36
+ "--opname", "index.html",
37
+ "--line-numbers",
38
+ "--main", "README",
39
+ "--inline-source"]
40
+
41
+ class Hoe
42
+ def extra_deps
43
+ @extra_deps.reject! { |x| Array(x).first == 'hoe' }
44
+ @extra_deps
45
+ end
46
+ end
47
+
48
+ # Generate all the Rake tasks
49
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
50
+ hoe = Hoe.new(GEM_NAME, VERS) do |p|
51
+ p.author = AUTHOR
52
+ p.description = DESCRIPTION
53
+ p.email = EMAIL
54
+ p.summary = DESCRIPTION
55
+ p.url = HOMEPATH
56
+ p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
57
+ p.test_globs = ["test/**/test_*.rb"]
58
+ p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
59
+
60
+ # == Optional
61
+ p.changes = p.paragraphs_of("History.txt", 0..1).join("\\n\\n")
62
+ #p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
63
+
64
+ #p.spec_extras = {} # A hash of extra values to set in the gemspec.
65
+
66
+ end
67
+
68
+ CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
69
+ PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
70
+ hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
@@ -0,0 +1,17 @@
1
+ require 'fileutils'
2
+ include FileUtils
3
+
4
+ require 'rubygems'
5
+ %w[rake hoe newgem rubigen].each do |req_gem|
6
+ begin
7
+ require req_gem
8
+ rescue LoadError
9
+ puts "This Rakefile requires the '#{req_gem}' RubyGem."
10
+ puts "Installation: gem install #{req_gem} -y"
11
+ exit
12
+ end
13
+ end
14
+
15
+ $:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
16
+
17
+ require 'cardia'
@@ -0,0 +1,207 @@
1
+ module Cardia
2
+
3
+ # This class represents a credit card transaction against Cardia
4
+ # These are created at Cardia using one of the register methods:
5
+ # - register, which keeps the control with us (we dont redirect the user away), but gives the merchant full responsibility
6
+ # - register_3d, which forces our user into foreign territory, hopefully returning to us afterwards; but limits the merchants liabilities
7
+ #
8
+ # == Minimal usage example
9
+ # require 'cardia'
10
+ #
11
+ # customer = Cardia::Customer.new("John","Doe")
12
+ # amount = 1000
13
+ # order = Cardia::Order.new(amount, "My test order")
14
+ # order.customer = customer
15
+ # merchant = Cardia::Merchant.test_merchant
16
+ # transaction = Cardia::CreditCardTransaction.with_http_connection(order)
17
+ # transaction.merchant = merchant
18
+ #
19
+ # card = ActiveMerchant::Billing::CreditCard.valid_card_for_testing
20
+ #
21
+ # status = transaction.register(card)
22
+ #
23
+ class CreditCardTransaction
24
+
25
+ attr_reader :order, :verification_address
26
+ attr_reader :merchant
27
+
28
+ def initialize(an_order, http_connection) #:nodoc:
29
+ @order= an_order
30
+ @http = http_connection
31
+ end
32
+
33
+ # Creates a new instance. Send along your Order as the parameter, see Billing::Order for methods that should be implemented in your order
34
+ def self.with_http_connection(an_order)
35
+ http = Net::HTTP.new(self.server_name,"443")
36
+ http.use_ssl = true
37
+ return self.new(an_order, http)
38
+ end
39
+
40
+ # Send along the Cardia::Merchant that the payment is for
41
+ def merchant=(a_merchant)
42
+ @merchant = a_merchant
43
+ end
44
+
45
+ def invoke_transaction_web_service(method, values) #:nodoc:
46
+ doc = do_invoke_web_service("/Service/Card/Transaction/1.2/Transaction.asmx/",
47
+ method,
48
+ values)
49
+ status_code = doc.root.elements["StatusCode"].text.to_i
50
+ return status_code, doc
51
+ end
52
+
53
+ def invoke_teller_web_service(method, values) #:nodoc:
54
+ doc = do_invoke_web_service("/Service/Card/Mpi/TellerMpi/1.2/TellerMpi.asmx/", method, values)
55
+ status_code = doc.root.elements["ResponseCode"].text.to_i
56
+ return status_code, doc
57
+ end
58
+
59
+ # Implements the vanilla plain calls to Cardia
60
+ def invoke(method, values) #:nodoc:
61
+ status, doc = invoke_transaction_web_service(method, values)
62
+ return status
63
+ end
64
+
65
+ # Register the payment, keeping our user with us
66
+ def register_with_doc(a_credit_card) #:nodoc:
67
+ raise "Invalid credit card" unless a_credit_card.valid?
68
+ params =
69
+ {
70
+ :merchantToken => @merchant.token,
71
+ :userToken => @order.customer.token,
72
+ :merchantReference => @order.reference,
73
+ :store => store_name,
74
+ :orderDescription => @order.description,
75
+ :cardNumber => a_credit_card.number,
76
+ :expiryDate => a_credit_card.expires,
77
+ :cvc2Code => a_credit_card.verification_value,
78
+ :currencyCode => "NOK",
79
+ :instanceId => "",
80
+ :amount => @order.amount.to_i, # croaks on floating point
81
+ :purchaseDate => Date.today.strftime("%Y-%m-%d"),
82
+ :isOnHold => "true",
83
+ :isPreRegistered => "false"
84
+ }
85
+ opts = []
86
+ params.each{|k,v| opts<<"#{k}=#{v}"}
87
+
88
+ values = opts.join("&")
89
+ method = "RegisterCardTransaction"
90
+ status, doc = invoke_transaction_web_service(method, values)
91
+ return status, doc
92
+ end
93
+
94
+ # Register the transaction with Cardia. Once this has been done, the amount is reserved on the users credit card, given that the the returned status says so...
95
+ def register(a_credit_card)
96
+ status, doc = register_with_doc(a_credit_card)
97
+ return status
98
+ end
99
+
100
+ def wait_until_approved #:nodoc:
101
+ (1..10).each do |n|
102
+ status = check_status
103
+ if status == TransactionStatus.approved
104
+ yield
105
+ return
106
+ end
107
+ puts n
108
+ sleep(2)
109
+ end
110
+ raise "Out of retries, Order not approved"
111
+ end
112
+
113
+ def http_connection
114
+ @http
115
+ end
116
+
117
+ def do_invoke_web_service(base_url, method, values) #:nodoc:
118
+ http = http_connection
119
+ url = base_url + method
120
+ response, body = http.post(url, values, {"Content-Type" => "application/x-www-form-urlencoded"})
121
+ response.value
122
+ return REXML::Document.new(response.body)
123
+ end
124
+
125
+ # Register using 3DSecure. After doing this, we should redirect the customer to the location stored in @verification_address
126
+ def register_3d(a_credit_card, success_url, failure_url)
127
+ raise "No verification code supplied" if a_credit_card.verification_value.nil?
128
+ params = {
129
+ :merchantToken => @merchant.token,
130
+ :applicationIdentifier => "", # not in use
131
+ :cardNumber => a_credit_card.number,
132
+ :expiryDate => a_credit_card.expires,
133
+ :cv2Code => a_credit_card.verification_value,
134
+ :merchantReference => @order.reference,
135
+ :successfulTransactionUrl => CGI::escape(success_url),
136
+ :unsuccessfulTransactionUrl => CGI::escape(failure_url),
137
+ :authorizedNotAuthenticatedUrl => "", # not in use
138
+ :currencyCode => "NOK",
139
+ :amount => @order.amount.to_i,
140
+ :isOnHold => "true",
141
+ :paymentType => a_credit_card.payment_type
142
+ }
143
+ opts = []
144
+ params.each{|k,v| opts << "#{k}=#{v}"}
145
+ values = opts.join("&")
146
+ status, doc = invoke_teller_web_service("PrepareTransaction", values)
147
+ unless status == 1
148
+ raise doc.root.elements["Error"].text
149
+ end
150
+ address = doc.root.elements["Address"].text
151
+ guid = doc.root.elements["ReferenceGuid"].text
152
+ @verification_address = "#{address}?preparationGuid=#{guid}"
153
+ return @verification_address
154
+ end
155
+
156
+
157
+ # Cancels a transaction. A good practice is probably to have a before_destroy filter in your order that does this
158
+ def void
159
+ return invoke("VoidTransaction", "merchantToken=#{@merchant.token}&userToken=#{@order.customer.token}&merchantReference=#{@order.reference}")
160
+ end
161
+
162
+
163
+ # Credits the transaction. This is bank-speak for reversing it; giving the customer a full refund
164
+ # If you want to, you can supply the amount manually
165
+ def credit(amount=nil)
166
+ amount = @order.amount if amount.nil?
167
+ return invoke("CreditTransaction", "merchantToken=#{@merchant.token}&amount=#{amount}&merchantReference=#{@order.reference}")
168
+ end
169
+ alias_method :refund, :credit
170
+
171
+ # This method confirms payments that are "on hold". Cardia calls this RevertOnHold
172
+ def confirm!
173
+ return invoke_transaction_web_service("RevertOnHoldTransaction", "merchantToken=#{@merchant.token}&merchantReference=#{@order.reference}")
174
+ end
175
+
176
+ # This method changes the amount on a transaction
177
+ # You should probably investigate about the terms around this before actually using it
178
+ def change_amount(new_amount)
179
+ return invoke("ChangeAmount", "merchantToken=#{@merchant.token}&amount=#{new_amount}&merchantReference=#{@order.reference}")
180
+ end
181
+
182
+ # Returns the transaction status as a Cardia::TransactionStatus instance
183
+ def check_status
184
+ status, doc = invoke_transaction_web_service("ReturnTransactionStatus", "merchantToken=#{@merchant.token}&merchantReference=#{@order.reference}")
185
+ response_code = doc.root.elements["ResponseCode"].text
186
+ currency_code = doc.root.elements["CurrencyCode"].text
187
+ recurring_code = doc.root.elements["RecurringCode"].text
188
+ amt = doc.root.elements["Amount"]
189
+ s = TransactionStatus.new(status)
190
+ unless amt.nil?
191
+ s.amount = Money.kroner(amt.text.to_i)
192
+ end
193
+ s.response_code = response_code.to_i
194
+ s.currency_code = currency_code
195
+ s.recurring_code = recurring_code
196
+ return s
197
+ end
198
+
199
+ def store_name #:nodoc:
200
+ "AS Juks og Bedrag"
201
+ end
202
+
203
+ def self.server_name #:nodoc:
204
+ "secure.cardia.no"
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,15 @@
1
+ module Cardia
2
+
3
+ # A very basic Customer, you probably want to roll your own
4
+ class Customer
5
+ def initialize(first_name, last_name)
6
+ @first_name, @last_name = first_name, last_name
7
+ end
8
+
9
+ # You need to generate a unique token identifying your Customer
10
+ def token
11
+ "867B5C54-B763-4D7C-B180-8766A5505656"
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,23 @@
1
+ module Cardia
2
+
3
+ # A merchant is the entity who requests payments/transactions. It is identified by a merchant token, which you'll get from Cardia
4
+ class Merchant
5
+ attr_accessor :token
6
+ class << self
7
+
8
+ # The test merchant. Using this merchant, no transactions are done for real.
9
+ def test_merchant
10
+ m = self.new
11
+ m.token = "CF4B6B54-6C28-4FA3-86B0-E9A347D75C6C"
12
+ m
13
+ end
14
+
15
+ # Create an instance with your real token
16
+ def with_token(a_token)
17
+ m = self.new
18
+ m.token = a_token
19
+ m
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,37 @@
1
+ module Cardia
2
+ # This is the very minimum that's required in order for and Order to function. You probably have your own...
3
+ # For home baked order classes, you need to implement methods listed below
4
+ class Order
5
+ attr_reader :amount, :description, :reference
6
+ attr_accessor :customer, :status
7
+
8
+ # Creates an instance: Parameters:
9
+ # * +amount+: The amount as an integer
10
+ # * +description+: Give a describing name (not used for much...)
11
+ def initialize(amount, description)
12
+ @amount,@description = amount, description
13
+ @reference = rand(10000)
14
+ end
15
+
16
+ # You need to provide a reference that's guaranteed to be unique for your merchant
17
+ def reference
18
+ @reference
19
+ end
20
+
21
+ def compute_access_key #:nodoc:
22
+ "ello"
23
+ end
24
+
25
+ # Callback that's called when an order is confirmed
26
+ def be_confirmed
27
+ @status = Status.confirmed
28
+ end
29
+
30
+ class Status #:nodoc:
31
+ def self.confirmed
32
+
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,60 @@
1
+ module Cardia
2
+ # Wrapper around the result of the ReturnTransactionStatus call to Cardia
3
+ class TransactionStatus < Enum
4
+ attr_accessor :amount, :response_code, :currency_code, :recurring_code
5
+ map :approved, 1, "Transaction is approved"
6
+ map :not_approved, 2, "Transaction not approved"
7
+ map :not_registered, 3, "No transaction registered"
8
+ map :error, 4, "General error"
9
+ map :void, 5, "Transaction is approved, but voided afterwards."
10
+
11
+ # Explains the current status
12
+ def explanation
13
+ if self.response_success?
14
+ return description
15
+ end
16
+ return "#{response_message}, #{description}"
17
+ end
18
+
19
+ # Whether the transaction was successful
20
+ def response_success?
21
+ response_code == 0
22
+ end
23
+
24
+ # Returns the response code as a human readable text
25
+ def response_message
26
+ result = response_code_to_string[response_code]
27
+ if result.nil?
28
+ result = "Unknown response code %s" % response_code
29
+ end
30
+ return result
31
+ end
32
+
33
+ def response_code_to_string #:nodoc:
34
+ result = {
35
+ 0 => "Success",
36
+ 6 => "Invalid card number",
37
+ 7 => "Card is hot listed (known card used for fraud)",
38
+ 17 => "Card is expired",
39
+ 18 => "Invalid expiry date",
40
+ 19 => "Card number is not valid",
41
+ 22 => "Card type is not registered for merchant",
42
+
43
+ 45 => "Amount exceeds allowed amount",
44
+
45
+ 46 => "Amount is below minimum amount",
46
+
47
+ 54 => "Card can not be used for internet purchases",
48
+
49
+ 69 => "The bank do not authorise the payment (authorisation not OK)",
50
+ 74 => "Card is rejected",
51
+
52
+ 75 => "Not authorised"}
53
+ [25, 26, 27, 28, 29, 31].each do |each|
54
+ result[each] = "Card can not be used for internet purchases"
55
+ end
56
+ return result
57
+ end
58
+ end
59
+
60
+ end
@@ -0,0 +1,9 @@
1
+ module Cardia #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/cardia.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'date'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'validateable'
5
+ require 'cgi'
6
+ require 'credit_card'
7
+ require 'rexml/document'
8
+ require 'enum'
9
+ require 'cardia/credit_card_transaction'
10
+ require 'cardia/customer'
11
+ require 'cardia/merchant'
12
+ require 'cardia/order'
13
+ require 'cardia/transaction_status'
14
+ require 'cardia/version'
15
+ $:.unshift File.dirname(__FILE__)
16
+
17
+ module Cardia
18
+
19
+ end