datatrans 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.2
@@ -3,29 +3,173 @@ Datatrans
3
3
 
4
4
  Ruby adapter for the Datatrans payment gateway (http://www.datatrans.ch).
5
5
 
6
- Usage
7
- =====
8
-
9
6
  Configuration
10
7
  -------------
11
8
 
9
+ Set your datatrans credentials in your environment.
10
+
12
11
  Datatrans.configure do |config|
13
12
  config.merchant_id = '1234567'
14
- config.sign_key = '...'
13
+ config.sign_key = 'ab739fd5b7c2a1...'
15
14
  config.environment = :production
16
15
  end
17
16
 
18
17
  Possible values for the environment: `:production`, `:development`
19
18
 
20
- Controller
21
- ----------
19
+ Web Authorization
20
+ =================
21
+
22
+ If you want to process a credit card the first time a web authorization is
23
+ necessary. Add the following code to a controller action that shows the form.
24
+ You need to pass at least `amount`, `currency` and `refno` (order number).
25
+
26
+ @transaction = Datatrans::Web::Transaction.new({
27
+ :amount => 1000, # in cents!
28
+ :currency => 'CHF',
29
+ :refno => 'ABCDEF',
30
+ :uppCustomerEmail => 'customer@email.com'
31
+ # feel free to add more upp infos here ...
32
+ })
33
+
34
+ In your View your show the credit card form with a convenient helper:
35
+
36
+ In this example we use just ECA (Mastercard) as paymentmethod. Feel free to
37
+ provide an appropriate select field to offer more payment methods. This is the
38
+ form you will show in your "new" method.
39
+
40
+ = form_tag Datatrans.web_authorize_url do
41
+
42
+ = text_field_tag :paymentmethod, 'ECA'
43
+ = text_field_tag :cardno
44
+ = text_field_tag :expm
45
+ = text_field_tag :expy
46
+ = text_field_tag :cvv
47
+
48
+ = hidden_field_tag :successUrl, <your_application_return_url>
49
+ = hidden_field_tag :cancelUrl, <your_application_return_url>
50
+ = hidden_field_tag :errorUrl, <your_application_return_url>
51
+
52
+ = datatrans_notification_request_hidden_fields(@transaction)
53
+
54
+ = submit_tag "send"
55
+
56
+ In this example we use just ECA (Mastercard) as paymentmethod. Feel free to
57
+ provide an appropriate select field to offer more payment methods. Don't forget
58
+ to add `successUrl`, `cancelUrl` and `errorUrl`. We recommend to set them all
59
+ to the same value.
60
+
61
+ After you submit the request to Datatrans they redirect back to your application.
62
+ Now you can process the transaction like this:
63
+
64
+ begin
65
+ transaction = Datatrans::Web::Transaction.new(params)
66
+
67
+ if transaction.authorize
68
+ # transaction was successful, access the following attributes
69
+ # transaction.transaction_id
70
+ # transaction.creditcard_alias
71
+ # transaction.masked_cc
72
+ # transaction.authorization_code
73
+ # ...
74
+
75
+ else
76
+ # transaction was not successful, accces the error details
77
+ # transaction.error_code, transaction.error_message, transaction.error_detail
78
+
79
+ end
80
+ rescue Datatrans::InvalidSignatureError => exception
81
+ # the signature was wrong, the request may have been compromised...
82
+ end
83
+
84
+ XML Transactions
85
+ ================
86
+
87
+ If you have already a credit card alias or an authorized transaction you can
88
+ use the convenient XML methods to process payments.
89
+
90
+ Authorize
91
+ ---------
92
+
93
+ transaction = Datatrans::XML::Transaction.new(
94
+ :refno => 'ABCDEF',
95
+ :amount => 1000, # in cents!
96
+ :currency => 'CHF',
97
+ :aliasCC => '8383843729284848348',
98
+ :expm => 12,
99
+ :expy => 15
100
+ )
101
+
102
+ if transaction.authorize
103
+ # ok, the transaction is authorized...
104
+ # access same values as in the web authorization (e.g. transaction.transaction_id)
105
+ else
106
+ # transaction.error_code, transaction.error_message, transaction.error_detail
107
+ end
108
+
109
+
110
+ Capture
111
+ -------
112
+
113
+ To capture an authorized transaction you use the following code:
22
114
 
23
- TODO
115
+ transaction = Datatrans::XML::Transaction.new(
116
+ :refno => 'ABCDEF',
117
+ :amount => 1000, # in cents!
118
+ :currency => 'CHF',
119
+ :transaction_id => 19834324987349723948729834
120
+ )
121
+
122
+ begin
123
+ if transaction.capture
124
+ # ok, the money is yours...
125
+ else
126
+ # transaction.error_code, transaction.error_message, transaction.error_detail
127
+ end
128
+ rescue Datatrans::InvalidSignatureError => exception
129
+ # the signature was wrong, the request may have been compromised...
130
+ end
131
+
24
132
 
25
- View
133
+ Void
26
134
  ----
27
135
 
28
- TODO
136
+ To make an authorized transaction invalid use void.
137
+
138
+ transaction = Datatrans::XML::Transaction.new(
139
+ :refno => 'ABCDEF',
140
+ :amount => 1000, # in cents!
141
+ :currency => 'CHF',
142
+ :transaction_id => 19834324987349723948729834
143
+ )
144
+
145
+ if transaction.void
146
+ # ok, the transaction is not longer valid...
147
+ else
148
+ # transaction.error_code, transaction.error_message, transaction.error_detail
149
+ end
150
+
151
+
152
+ Todo
153
+ ====
154
+
155
+ * allow signing of xml transactions
156
+ * allow signing with different keys
157
+ * add credit method to reverse already captured transactions
158
+ * add purchase method to authorize and capture in one step
159
+ * dry code more
160
+
161
+
162
+ Contribute
163
+ ==========
164
+
165
+ * Fork the project.
166
+ * Make your feature addition or bug fix.
167
+ * Add specs for it. This is important so we don't break it in a
168
+ future version unintentionally.
169
+ * Commit, do not mess with rakefile, version, or history.
170
+ (if you want to have your own version, that is fine but bump version in a commit by itself we can ignore when we pull)
171
+ * Send us a pull request. Bonus points for topic branches.
172
+
29
173
 
30
174
  Credits
31
175
  =======
@@ -20,4 +20,9 @@ Gem::Specification.new do |s|
20
20
 
21
21
  s.add_dependency 'httparty'
22
22
  s.add_dependency 'activesupport', '>= 3.0.0'
23
+ s.add_dependency 'i18n'
24
+ s.add_dependency 'builder'
25
+
26
+ s.add_development_dependency 'rspec'
27
+ s.add_development_dependency 'actionpack', '>= 3.0.0'
23
28
  end
@@ -1,3 +1,6 @@
1
+ require 'active_support/core_ext/module'
2
+ require 'action_view'
3
+
1
4
  module Datatrans
2
5
  BASE_URL = 'https://payment.datatrans.biz'
3
6
  WEB_AUTHORIZE_URL = "#{BASE_URL}/upp/jsp/upStart.jsp"
@@ -38,10 +41,13 @@ module Datatrans
38
41
  raise "Unknown environment '#{environment}'. Available: :development, :production."
39
42
  end
40
43
  end
44
+
45
+ class InvalidSignatureError < StandardError; end
41
46
  end
42
47
 
43
48
  require 'datatrans/version'
44
- require 'datatrans/notification'
45
- require 'datatrans/transaction'
49
+ require 'datatrans/common'
50
+ require 'datatrans/xml/transaction'
51
+ require 'datatrans/web/transaction'
46
52
 
47
- ActionView::Base.send :include, Datatrans::Notification::ViewHelper if defined? ActionView
53
+ ActionView::Base.send :include, Datatrans::Web::ViewHelper if defined? ActionView
@@ -0,0 +1,9 @@
1
+ require 'openssl'
2
+
3
+ module Datatrans::Common
4
+ def sign(*fields)
5
+ key = Datatrans.sign_key.split(/([a-f0-9][a-f0-9])/).reject(&:empty?)
6
+ key = key.pack("H*" * key.size)
7
+ OpenSSL::HMAC.hexdigest(OpenSSL::Digest::MD5.new, key, fields.join)
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module Datatrans
2
- VERSION = "1.0.0"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -0,0 +1,55 @@
1
+ require 'active_support/core_ext/hash'
2
+
3
+ module Datatrans::Web
4
+ class Transaction
5
+ include Datatrans::Common
6
+
7
+ attr_accessor :request
8
+ attr_reader :response, :params
9
+
10
+ def initialize(params)
11
+ raise 'Please define Datatrans.sign_key!' unless Datatrans.sign_key.present?
12
+
13
+ params = params.to_hash
14
+ params.symbolize_keys!
15
+ params.reverse_merge!({ :reqtype => 'NOA', :useAlias => 'Yes', :hiddenMode => 'Yes' })
16
+ @params = params
17
+ end
18
+
19
+ def signature
20
+ sign(Datatrans.merchant_id, params[:amount], params[:currency], params[:refno])
21
+ end
22
+
23
+ def authorize
24
+ @response = AuthorizeResponse.new(params)
25
+ @response.successful?
26
+ end
27
+
28
+ def method_missing(method, *args, &block)
29
+ if response.respond_to? method.to_sym
30
+ response.send(method)
31
+ else
32
+ super
33
+ end
34
+ end
35
+ end
36
+
37
+ module ViewHelper
38
+ def datatrans_notification_request_hidden_fields(transaction)
39
+ [
40
+ hidden_field_tag(:merchantId, Datatrans.merchant_id),
41
+ hidden_field_tag(:hiddenMode, transaction.params[:hiddenMode]),
42
+ hidden_field_tag(:reqtype, transaction.params[:reqtype]),
43
+ hidden_field_tag(:amount, transaction.params[:amount]),
44
+ hidden_field_tag(:currency, transaction.params[:currency]),
45
+ hidden_field_tag(:useAlias, transaction.params[:useAlias]),
46
+ hidden_field_tag(:sign, transaction.signature),
47
+ hidden_field_tag(:refno, transaction.params[:refno]),
48
+ hidden_field_tag(:uppCustomerName, transaction.params[:uppCustomerName]),
49
+ hidden_field_tag(:uppCustomerEmail, transaction.params[:uppCustomerEmail])
50
+ ].join.html_safe
51
+ end
52
+ end
53
+ end
54
+
55
+ require 'datatrans/web/transaction/authorize'
@@ -0,0 +1,68 @@
1
+ class Datatrans::Web::Transaction
2
+ class AuthorizeResponse
3
+ attr_accessor :params
4
+
5
+ def initialize(params)
6
+ @params = params
7
+ end
8
+
9
+ def successful?
10
+ raise Datatrans::InvalidSignatureError unless valid_signature?
11
+ response_code == '01' && response_message == 'Authorized' && !errors_occurred?
12
+ end
13
+
14
+ def valid_signature?
15
+ return true if errors_occurred? # no sign2 sent on error
16
+ sign(Datatrans.merchant_id, params[:amount], params[:currency], params[:uppTransactionId]) == params[:sign2]
17
+ end
18
+
19
+ def response_code
20
+ params[:responseCode] rescue nil
21
+ end
22
+
23
+ def response_message
24
+ params[:responseMessage] rescue nil
25
+ end
26
+
27
+ def transaction_id
28
+ params[:uppTransactionId] rescue nil
29
+ end
30
+
31
+ def reference_number
32
+ params[:refno] rescue nil
33
+ end
34
+
35
+ def authorization_code
36
+ params[:authorizationCode] rescue nil
37
+ end
38
+
39
+ def masked_cc
40
+ params[:maskedCC] rescue nil
41
+ end
42
+
43
+ def creditcard_alias
44
+ params[:aliasCC] rescue nil
45
+ end
46
+
47
+ def error_code
48
+ params[:errorCode] rescue nil
49
+ end
50
+
51
+ def error_message
52
+ params[:errorMessage] rescue nil
53
+ end
54
+
55
+ def error_detail
56
+ params[:errorDetail] rescue nil
57
+ end
58
+
59
+
60
+ private
61
+
62
+ def errors_occurred?
63
+ error_code || error_message || error_detail
64
+ end
65
+
66
+ include Datatrans::Common
67
+ end
68
+ end
@@ -0,0 +1,47 @@
1
+ require 'active_support/core_ext/hash'
2
+
3
+ module Datatrans::XML
4
+ class Transaction
5
+ attr_accessor :request
6
+ attr_reader :response, :params
7
+
8
+ def initialize(params)
9
+ @params = params.symbolize_keys
10
+ end
11
+
12
+ def authorize
13
+ self.request = AuthorizeRequest.new(params)
14
+ @response = AuthorizeResponse.new(request.process)
15
+ @response.successful?
16
+ end
17
+
18
+ def void
19
+ self.request = VoidRequest.new(params)
20
+ @response = VoidResponse.new(request.process)
21
+ @response.successful?
22
+ end
23
+
24
+ def capture
25
+ self.request = CaptureRequest.new(params)
26
+ @response = CaptureResponse.new(request.process)
27
+ @response.successful?
28
+ end
29
+
30
+ # TODO: purchase, credit methods
31
+
32
+
33
+ def method_missing(method, *args, &block)
34
+ if response.respond_to? method.to_sym
35
+ response.send(method)
36
+ elsif request.respond_to? method.to_sym
37
+ request.send(method)
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ require 'datatrans/xml/transaction/authorize'
46
+ require 'datatrans/xml/transaction/void'
47
+ require 'datatrans/xml/transaction/capture'
@@ -0,0 +1,83 @@
1
+ require 'datatrans/xml/transaction/request'
2
+ require 'datatrans/xml/transaction/response'
3
+
4
+ class Datatrans::XML::Transaction
5
+ class AuthorizeRequest < Request
6
+
7
+ def process
8
+ self.class.post(Datatrans.xml_authorize_url,
9
+ :headers => { 'Content-Type' => 'text/xml' },
10
+ :body => build_authorize_request.to_s).parsed_response
11
+ end
12
+
13
+
14
+ private
15
+
16
+ def build_authorize_request
17
+ build_xml_request(:authorization) do |xml|
18
+ xml.amount params[:amount]
19
+ xml.currency params[:currency]
20
+ xml.aliasCC params[:aliasCC]
21
+ xml.expm params[:expm]
22
+ xml.expy params[:expy]
23
+ xml.sign sign(Datatrans.merchant_id, params[:amount], params[:currency], params[:refno])
24
+ end
25
+ end
26
+
27
+ end
28
+
29
+ class AuthorizeResponse < Response
30
+
31
+ def successful?
32
+ response_code == '01' && response_message == 'Authorized'
33
+ end
34
+
35
+ def response_code
36
+ params_root_node['response']['responseCode'] rescue nil
37
+ end
38
+
39
+ def response_message
40
+ params_root_node['response']['responseMessage'] rescue nil
41
+ end
42
+
43
+ def transaction_id
44
+ params_root_node['response']['uppTransactionId'] rescue nil
45
+ end
46
+
47
+ def reference_number
48
+ params_root_node['refno'] rescue nil
49
+ end
50
+
51
+ def authorization_code
52
+ params_root_node['response']['authorizationCode'] rescue nil
53
+ end
54
+
55
+ def masked_cc
56
+ params_root_node['response']['maskedCC'] rescue nil
57
+ end
58
+
59
+ def creditcard_alias
60
+ params_root_node['request']['aliasCC'] rescue nil
61
+ end
62
+
63
+ def error_code
64
+ params_root_node['error']['errorCode'] rescue nil
65
+ end
66
+
67
+ def error_message
68
+ params_root_node['error']['errorMessage'] rescue nil
69
+ end
70
+
71
+ def error_detail
72
+ params_root_node['error']['errorDetail'] rescue nil
73
+ end
74
+
75
+
76
+ private
77
+
78
+ def params_root_node
79
+ params['authorizationService']['body']['transaction']
80
+ end
81
+
82
+ end
83
+ end