cardia 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/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