active_merchant_sermepa 0.1.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.
@@ -0,0 +1,5 @@
1
+ tmp/*
2
+ nbproject
3
+ .svn
4
+ .DS_Store
5
+ *.swp
@@ -0,0 +1,3 @@
1
+
2
+ v0.0.1. First import from active merchant repo
3
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+
2
+ source :rubygems
3
+ gemspec
4
+
@@ -0,0 +1,54 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ active_merchant_sermepa (0.0.1)
5
+ activemerchant (>= 1.9.4)
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ abstract (1.0.0)
11
+ actionpack (3.0.4)
12
+ activemodel (= 3.0.4)
13
+ activesupport (= 3.0.4)
14
+ builder (~> 2.1.2)
15
+ erubis (~> 2.6.6)
16
+ i18n (~> 0.4)
17
+ rack (~> 1.2.1)
18
+ rack-mount (~> 0.6.13)
19
+ rack-test (~> 0.5.7)
20
+ tzinfo (~> 0.3.23)
21
+ activemerchant (1.11.0)
22
+ activesupport (>= 2.3.8)
23
+ braintree (>= 2.0.0)
24
+ builder (>= 2.0.0)
25
+ activemodel (3.0.4)
26
+ activesupport (= 3.0.4)
27
+ builder (~> 2.1.2)
28
+ i18n (~> 0.4)
29
+ activesupport (3.0.4)
30
+ braintree (2.8.0)
31
+ builder
32
+ builder (2.1.2)
33
+ erubis (2.6.6)
34
+ abstract (>= 1.0.0)
35
+ i18n (0.5.0)
36
+ mocha (0.9.12)
37
+ money (3.5.5)
38
+ i18n (~> 0.4)
39
+ rack (1.2.1)
40
+ rack-mount (0.6.13)
41
+ rack (>= 1.0.0)
42
+ rack-test (0.5.7)
43
+ rack (>= 1.0)
44
+ tzinfo (0.3.24)
45
+
46
+ PLATFORMS
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ actionpack (~> 3.0.3)
51
+ active_merchant_sermepa!
52
+ activemerchant (>= 1.9.4)
53
+ mocha (~> 0.9.10)
54
+ money (~> 3.5.4)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010-2011 Samuel Lown
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.
@@ -0,0 +1,15 @@
1
+ CHANGELOG
2
+ MIT-LICENSE
3
+ Manifest
4
+ README.rdoc
5
+ Rakefile
6
+ active_merchant_sermepa.gemspec
7
+ lib/active_merchant/billing/integrations/sermepa.rb
8
+ lib/active_merchant/billing/integrations/sermepa/helper.rb
9
+ lib/active_merchant/billing/integrations/sermepa/notification.rb
10
+ lib/active_merchant/billing/integrations/sermepa/return.rb
11
+ lib/active_merchant_sermepa.rb
12
+ test/test_helper.rb
13
+ test/unit/integrations/helpers/sermepa_helper_test.rb
14
+ test/unit/integrations/notifications/sermepa_notification_test.rb
15
+ test/unit/integrations/sermepa_module_text.rb
@@ -0,0 +1,158 @@
1
+ = Active Merchant Sermepa Plugin
2
+
3
+ Basic support for the Spanish SERMEPA Virtual POS payment gateway provided by Servired.
4
+ Used by many banks in Spain and known locally as a "TPV Virtual".
5
+
6
+ Include this library along with the standard active merchant package and a new
7
+ Integration payment gateway will be available.
8
+
9
+
10
+ == Install
11
+
12
+ Add to your Gemfile as follows:
13
+
14
+ gem 'activemerchant_sermepa'
15
+
16
+ That should automatically install a usable version of active merchant as a dependency.
17
+
18
+ == Usage
19
+
20
+ This is an integrated payment gateway and as such requires all credit card details
21
+ to be provided outside of your own website's pages. The basic purchase process is:
22
+
23
+ 1. HTML form shown on website with payment details
24
+ 2. Form is submitted directly to Sermepa's servers
25
+ 3. User provides credit card and completes validatation proceedures (this can be a pain in the *** for your clients if they don't have the necessary login details, ask your bank to disable it if you get lots of complaints.)
26
+ 4. Optional: website receives notification from sermepa of sale result
27
+ 5. User forwarded back to your website. If the "Parameters in URL" (Parámetros en las URLs) option is set in the point of sale's configuration, the user will be forwarded with a set of parameters that can be used to validate the purchase.
28
+ 6. Confirm that the purchase is successful using either notification or parameters in URL.
29
+
30
+ Sermepa, unlike the similar BBVA system, does not support manual requests to confirm
31
+ the success of a sale. Validation must be done using the parameters received either
32
+ from the notification or paremeters in forwarded URL.
33
+
34
+
35
+ === Configuration
36
+
37
+ Your bank should provide you with three keys, known as the "terminal id", "commercial id",
38
+ and "secret key". These when combined allow requests to be signed and incoming confirmations
39
+ to be validated. A final option is available to set the the type of key used in the
40
+ transactions. The "key type" can be set to either "sha1_complete" or "sha1_extended
41
+ according to whatever is set by your bank.
42
+
43
+ These configuration options can either be set globally or on a per-request basis. The
44
+ following would be included in an initializer to prepare the gateway for use:
45
+
46
+ ActiveMerchant::Billing::Integrations::Sermepa::Helper.credentials = {
47
+ :terminal_id => '9',
48
+ :commercial_id => ''999008881,
49
+ :secret_key => 'qwertyasdf0123456789',
50
+ :key_type => 'sha1_complete'
51
+ }
52
+
53
+ If the credentials are not set this way, they'll need to be included with each call to
54
+ the library's methods.
55
+
56
+ === Form
57
+
58
+ Active Merchant provides a helper called "payment_service_for" which handles the preparation
59
+ of the request to the gateway. The following code sample shows what this might look like:
60
+
61
+ = payment_service_for @invoice.transactions.last.code, 'The Shop', :amount => @invoice.total.cents, :currency => 'EUR', :service => :sermepa do |service|
62
+ - service.description "Some description of the purchase"
63
+ - service.customer_name @invoice.client.name
64
+ - service.notify_url notify_invoice_url(@invoice)
65
+ - service.success_url complete_invoice_url(@invoice)
66
+ - service.failure_url complete_invoice_url(@invoice)
67
+
68
+ = submit_tag "Go to payment gateway!"
69
+
70
+ A few important things to bare in mind:
71
+
72
+ - Each request to the service *must* have a unique transaction or order id. This is the first parameter provided to the helper.
73
+ - If a purchase fails, the order id *must* be updated.
74
+ - As per the Sermepa documentation the transaction id must be between 4 and 12 digits long and always start with 4 numbers.
75
+ - The credentials can be provided in the ":credentials" option to the helper if preferred.
76
+ - The URLs set where the user will be sent after the purchase.
77
+
78
+
79
+ === Notification and Confirmation
80
+
81
+ While confirming the purchase is optional, it is highly recommended as it allows you
82
+ to let the client know instantly that the transaction has completed successfully.
83
+
84
+ If HTTP notification has been enabled in the Sermepa configuration, a private
85
+ request will be sent from the gateway to your website confirming the success of
86
+ the transaction. The notification URL can be provided either in the form's parameters
87
+ or in the administrator configuration. You'll most likely not be able to test this
88
+ during development on your local machine for obvious reasons.
89
+
90
+ Ensuring that the parameters are provided from the returning user is much more
91
+ convenient for testing and allows for an instant response to the user.
92
+
93
+ Both types work using the same parameters in the URL so can be handled with the same method.
94
+
95
+ Your controller might have a actions like in the following example:
96
+
97
+ # Receive a direct notification from the gateway
98
+ def notify
99
+ notify = ActiveMerchant::Billing::Integrations::Sermepa.notification(request.query_parameters)
100
+ if notify.acknowledge
101
+ # Do something useful
102
+ end
103
+ render :text => 'OK'
104
+ end
105
+
106
+ # Handle the incoming user
107
+ def confirm
108
+ notify = ActiveMerchant::Billing::Integrations::Sermepa.notification(request.query_parameters)
109
+ if notify.acknowledge
110
+ # do something useful
111
+ render :action => 'success'
112
+ else
113
+ render :action => 'failure'
114
+ end
115
+ end
116
+
117
+ These examples are greatly simplified. It would be much better to handle these operations in
118
+ a model and provide support for recording each event that takes place.
119
+
120
+ The acknowledge method also accepts a hash of credentials should you prefer this
121
+ to setting them globally. It checks to see if by combining the received parameters
122
+ the same signature can be generated using our secret key. Assuming the operation
123
+ successful and the signature is valid, the purchase will be successful.
124
+
125
+
126
+ == Authors
127
+
128
+ Written by Sam Lown.
129
+
130
+ Special thanks go to {rentages.es}[http://www.rentages.es] for supporting the initial development.
131
+
132
+ Thanks should also go to {floresfrescas.com}[http://www.floresfrescas.com] for supporting the development of the BBVA TPV gateway method on which this is heavily based.
133
+
134
+ And of course, many thanks to the ActiveMerchant[http://www.activemerchant.org] Shopify[http://www.shopify.com] team for releasing this library in the first place!
135
+
136
+ == License
137
+
138
+ Released under the MIT license.
139
+
140
+ Copyright (c) 2010-2011 Samuel Lown
141
+
142
+ Permission is hereby granted, free of charge, to any person obtaining a copy
143
+ of this software and associated documentation files (the "Software"), to deal
144
+ in the Software without restriction, including without limitation the rights
145
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
146
+ copies of the Software, and to permit persons to whom the Software is
147
+ furnished to do so, subject to the following conditions:
148
+
149
+ The above copyright notice and this permission notice shall be included in
150
+ all copies or substantial portions of the Software.
151
+
152
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
153
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
154
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
155
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
156
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
157
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
158
+ THE SOFTWARE.
@@ -0,0 +1,18 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require 'rake'
6
+ require 'rake/testtask'
7
+
8
+ desc "Run the unit test suite"
9
+ task :default => 'test:units'
10
+
11
+ namespace :test do
12
+ Rake::TestTask.new(:units) do |t|
13
+ t.pattern = 'test/unit/**/*_test.rb'
14
+ t.ruby_opts << '-rubygems'
15
+ t.libs << 'test'
16
+ t.verbose = true
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{active_merchant_sermepa}
5
+ s.version = "0.1.1"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.3.1") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Sam Lown"]
9
+ s.date = %q{2011-02-25}
10
+ s.description = %q{Add support to ActiveMerchant for the Sermepa payment gateway by Servired used by many banks in Spain}
11
+ s.summary = "ActiveMerchant support for Servired's Sermepa payment gateway"
12
+ s.email = %q{me@samlown.com}
13
+ s.extra_rdoc_files = ['MIT-LICENSE', 'CHANGELOG', 'README.rdoc']
14
+ s.homepage = %q{http://github.com/samlown/active_merchant_sermepa}
15
+ s.rubygems_version = "1.3.7"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_dependency("activemerchant", ">= 1.9.4")
23
+ s.add_development_dependency("mocha", "~> 0.9.10")
24
+ s.add_development_dependency("money", "~> 3.5.4")
25
+ s.add_development_dependency("actionpack", "~> 3.0.3")
26
+
27
+ end
@@ -0,0 +1,158 @@
1
+ # encoding: utf-8
2
+ module ActiveMerchant #:nodoc:
3
+ module Billing #:nodoc:
4
+ module Integrations #:nodoc:
5
+ # See the BbvaTpv::Helper class for more generic information on usage of
6
+ # this integrated payment method.
7
+ module Sermepa
8
+
9
+ autoload :Helper, 'active_merchant/billing/integrations/sermepa/helper.rb'
10
+ autoload :Return, 'active_merchant/billing/integrations/sermepa/return.rb'
11
+ autoload :Notification, 'active_merchant/billing/integrations/sermepa/notification.rb'
12
+
13
+ mattr_accessor :service_test_url
14
+ self.service_test_url = "https://sis-t.sermepa.es:25443/sis/realizarPago"
15
+ mattr_accessor :service_production_url
16
+ self.service_production_url = "https://sis.sermepa.es/sis/realizarPago"
17
+
18
+ mattr_accessor :operations_test_url
19
+ self.operations_test_url = "https://sis-t.sermepa.es:25443/sis/operaciones"
20
+ mattr_accessor :operations_production_url
21
+ self.operations_production_url = "https://sis.sermepa.es/sis/operaciones"
22
+
23
+
24
+ def self.service_url
25
+ mode = ActiveMerchant::Billing::Base.integration_mode
26
+ case mode
27
+ when :production
28
+ self.service_production_url
29
+ when :test
30
+ self.service_test_url
31
+ else
32
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
33
+ end
34
+ end
35
+
36
+ def self.operations_url
37
+ mode = ActiveMerchant::Billing::Base.integration_mode
38
+ case mode
39
+ when :production
40
+ self.operations_production_url
41
+ when :test
42
+ self.operations_test_url
43
+ else
44
+ raise StandardError, "Integration mode set to an invalid value: #{mode}"
45
+ end
46
+
47
+ end
48
+
49
+ def self.notification(post)
50
+ Notification.new(post)
51
+ end
52
+
53
+
54
+ def self.currency_code( name )
55
+ row = supported_currencies.assoc(name)
56
+ row.nil? ? supported_currencies.first[1] : row[1]
57
+ end
58
+
59
+ def self.currency_from_code( code )
60
+ row = supported_currencies.rassoc(code)
61
+ row.nil? ? supported_currencies.first[0] : row[0]
62
+ end
63
+
64
+ def self.language_code(name)
65
+ row = supported_languages.assoc(name.to_s.downcase.to_sym)
66
+ row.nil? ? supported_languages.first[1] : row[1]
67
+ end
68
+
69
+ def self.language_from_code( code )
70
+ row = supported_languages.rassoc(code)
71
+ row.nil? ? supported_languages.first[0] : row[0]
72
+ end
73
+
74
+ def self.transaction_code(name)
75
+ row = supported_transactions.assoc(name.to_sym)
76
+ row.nil? ? supported_transactions.first[1] : row[1]
77
+ end
78
+ def self.transaction_from_code(code)
79
+ row = supported_transactions.rassoc(code.to_s)
80
+ row.nil? ? supported_languages.first[0] : row[0]
81
+ end
82
+
83
+ def self.supported_currencies
84
+ [ ['EUR', '978'] ]
85
+ end
86
+
87
+ def self.supported_languages
88
+ [
89
+ [:es, '001'],
90
+ [:en, '002'],
91
+ [:ca, '003'],
92
+ [:fr, '004'],
93
+ [:de, '005'],
94
+ [:pt, '009']
95
+ ]
96
+ end
97
+
98
+ def self.supported_transactions
99
+ [
100
+ [:authorization, '0'],
101
+ [:preauthorization, '1'],
102
+ [:confirmation, '2'],
103
+ [:automatic_return, '3'],
104
+ [:reference_payment, '4'],
105
+ [:recurring_transaction, '5'],
106
+ [:successive_transaction, '6'],
107
+ [:authentication, '7'],
108
+ [:confirm_authentication, '8'],
109
+ [:cancel_preauthorization, '9'],
110
+ [:deferred_authorization, 'O'],
111
+ [:confirm_deferred_authorization, 'P'],
112
+ [:cancel_deferred_authorization, 'Q'],
113
+ [:inicial_recurring_authorization, 'R'],
114
+ [:successive_recurring_authorization, 'S']
115
+ ]
116
+ end
117
+
118
+ def self.response_code_message(code)
119
+ case code.to_i
120
+ when 0..99
121
+ nil
122
+ when 900
123
+ "Transacción autorizada para devoluciones y confirmaciones"
124
+ when 101
125
+ "Tarjeta caducada"
126
+ when 102
127
+ "Tarjeta en excepción transitoria o bajo sospecha de fraude"
128
+ when 104
129
+ "Operación no permitida para esa tarjeta o terminal"
130
+ when 116
131
+ "Disponible insuficiente"
132
+ when 118
133
+ "Tarjeta no registrada o Método de pago no disponible para su tarjeta"
134
+ when 129
135
+ "Código de seguridad (CVV2/CVC2) incorrecto"
136
+ when 180
137
+ "Tarjeta no válida o Tarjeta ajena al servicio o Error en la llamada al MPI sin controlar."
138
+ when 184
139
+ "Error en la autenticación del titular"
140
+ when 190
141
+ "Denegación sin especificar Motivo"
142
+ when 191
143
+ "Fecha de caducidad errónea"
144
+ when 202
145
+ "Tarjeta en excepción transitoria o bajo sospecha de fraude con retirada de tarjeta"
146
+ when 912,9912
147
+ "Emisor no disponible"
148
+ when 913
149
+ "Pedido repetido"
150
+ else
151
+ "Transacción denegada"
152
+ end
153
+ end
154
+
155
+ end
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,187 @@
1
+ # encoding: utf-8
2
+ module ActiveMerchant #:nodoc:
3
+ module Billing #:nodoc:
4
+ module Integrations #:nodoc:
5
+ module Sermepa
6
+ # Sermepa/Servired Spanish Virtual POS Gateway
7
+ #
8
+ # Support for the Spanish payment gateway provided by Sermepa, part of Servired,
9
+ # one of the main providers in Spain to Banks and Cajas.
10
+ #
11
+ # Requires the :terminal_id, :commercial_id, and :secret_key to be set in the credentials
12
+ # before the helper can be used. Credentials may be overwriten when instantiating the helper
13
+ # if required or instead of the global variable. Optionally, the :key_type can also be set to
14
+ # either 'sha1_complete' or 'sha1_extended', where the later is the default case. This
15
+ # is a configurable option in the Sermepa admin which you may or may not be able to access.
16
+ # If nothing seems to work, try changing this.
17
+ #
18
+ # Ensure the gateway is configured correctly. Synchronization should be set to Asynchronous
19
+ # and the parameters in URL option (Parámetros en las URLs) should be set to true unless
20
+ # the notify_url is provided. During development on localhost ensuring this option is set
21
+ # is especially important as there is no other way to confirm a successful purchase.
22
+ #
23
+ # Your view for a payment form might look something like the following:
24
+ #
25
+ # <%= payment_service_for @transaction.id, 'Company name', :amount => @transaction.amount, :currency => 'EUR', :service => :sermepa do |service| %>
26
+ # <% service.description @sale.description %>
27
+ # <% service.customer_name @sale.client.name %>
28
+ # <% service.notify_url notify_sale_url(@sale) %>
29
+ # <% service.success_url win_sale_url(@sale) %>
30
+ # <% service.failure_url fail_sale_url(@sale) %>
31
+ #
32
+ # <%= submit_tag "PAY!" %>
33
+ # <% end %>
34
+ #
35
+ #
36
+ #
37
+ class Helper < ActiveMerchant::Billing::Integrations::Helper
38
+ include PostsData
39
+
40
+ class << self
41
+ # Credentials should be set as a hash containing the fields:
42
+ # :terminal_id, :commercial_id, :secret_key, :key_type (optional)
43
+ attr_accessor :credentials
44
+ end
45
+
46
+ mapping :account, 'Ds_Merchant_MerchantName'
47
+
48
+ mapping :currency, 'Ds_Merchant_Currency'
49
+ mapping :amount, 'Ds_Merchant_Amount'
50
+
51
+ mapping :order, 'Ds_Merchant_Order'
52
+ mapping :description, 'Ds_Merchant_ProductDescription'
53
+ mapping :client, 'Ds_Merchant_Titular'
54
+
55
+ mapping :notify_url, 'Ds_Merchant_MerchantURL'
56
+ mapping :success_url, 'Ds_Merchant_UrlOK'
57
+ mapping :failure_url, 'Ds_Merchant_UrlKO'
58
+
59
+ mapping :language, 'Ds_Merchant_ConsumerLanguage'
60
+
61
+ mapping :transaction_type, 'Ds_Merchant_TransactionType'
62
+
63
+ mapping :customer_name, 'Ds_Merchant_Titular'
64
+
65
+ #### Special Request Specific Fields ####
66
+ mapping :signature, 'Ds_Merchant_MerchantSignature'
67
+ ########
68
+
69
+ # ammount should always be provided in cents!
70
+ def initialize(order, account, options = {})
71
+ self.credentials = options.delete(:credentials) if options[:credentials]
72
+ super(order, account, options)
73
+
74
+ add_field 'Ds_Merchant_MerchantCode', credentials[:commercial_id]
75
+ add_field 'Ds_Merchant_Terminal', credentials[:terminal_id]
76
+ #add_field mappings[:transaction_type], '0' # Default Transaction Type
77
+ self.transaction_type = :authorization
78
+ end
79
+
80
+ # Allow credentials to be overwritten if needed
81
+ def credentials
82
+ @credentials || self.class.credentials
83
+ end
84
+ def credentials=(creds)
85
+ @credentials = (self.class.credentials || {}).dup.merge(creds)
86
+ end
87
+
88
+ def amount=(money)
89
+ cents = money.respond_to?(:cents) ? money.cents : money
90
+ if money.is_a?(String) || cents.to_i <= 0
91
+ raise ArgumentError, 'money amount must be either a Money object or a positive integer in cents.'
92
+ end
93
+ add_field mappings[:amount], cents.to_i
94
+ end
95
+
96
+ def order=(order_id)
97
+ order_id = order_id.to_s
98
+ if order_id !~ /^[0-9]{4}/ && order_id.length <= 8
99
+ order_id = ('0' * 4) + order_id
100
+ end
101
+ regexp = /^[0-9]{4}[0-9a-zA-Z]{0,8}$/
102
+ raise "Invalid order number format! First 4 digits must be numbers" if order_id !~ regexp
103
+ add_field mappings[:order], order_id
104
+ end
105
+
106
+ def currency=( value )
107
+ add_field mappings[:currency], Sermepa.currency_code(value)
108
+ end
109
+
110
+ def language=(lang)
111
+ add_field mappings[:language], Sermepa.language_code(lang)
112
+ end
113
+
114
+ def transaction_type=(type)
115
+ add_field mappings[:transaction_type], Sermepa.transaction_code(type)
116
+ end
117
+
118
+ def form_fields
119
+ add_field mappings[:signature], sign_request
120
+ @fields
121
+ end
122
+
123
+
124
+ # Send a manual request for the currently prepared transaction.
125
+ # This is an alternative to the normal view helper and is useful
126
+ # for special types of transaction.
127
+ def send_transaction
128
+ body = build_xml_request
129
+
130
+ headers = { }
131
+ headers['Content-Length'] = body.size.to_s
132
+ headers['User-Agent'] = "Active Merchant -- http://activemerchant.org"
133
+ headers['Content-Type'] = 'application/x-www-form-urlencoded'
134
+
135
+ # Return the raw response data
136
+ ssl_post(Sermepa.operations_url, "entrada="+CGI.escape(body), headers)
137
+ end
138
+
139
+ protected
140
+
141
+ def build_xml_request
142
+ xml = Builder::XmlMarkup.new :indent => 2
143
+ xml.DATOSENTRADA do
144
+ xml.DS_Version 0.1
145
+ xml.DS_MERCHANT_CURRENCY @fields['Ds_Merchant_Currency']
146
+ xml.DS_MERCHANT_AMOUNT @fields['Ds_Merchant_Amount']
147
+ xml.DS_MERCHANT_MERCHANTURL @fields['Ds_Merchant_MerchantURL']
148
+ xml.DS_MERCHANT_TRANSACTIONTYPE @fields['Ds_Merchant_TransactionType']
149
+ xml.DS_MERCHANT_MERCHANTDATA @fields['Ds_Merchant_Product_Description']
150
+ xml.DS_MERCHANT_TERMINAL credentials[:terminal_id]
151
+ xml.DS_MERCHANT_MERCHANTCODE credentials[:commercial_id]
152
+ xml.DS_MERCHANT_ORDER @fields['Ds_Merchant_Order']
153
+ xml.DS_MERCHANT_MERCHANTSIGNATURE sign_request
154
+ end
155
+ xml.target!
156
+ end
157
+
158
+
159
+ # Generate a signature authenticating the current request.
160
+ # Values included in the signature are determined by the the type of
161
+ # transaction.
162
+ def sign_request
163
+ str = @fields['Ds_Merchant_Amount'].to_s +
164
+ @fields['Ds_Merchant_Order'].to_s +
165
+ @fields['Ds_Merchant_MerchantCode'].to_s +
166
+ @fields['Ds_Merchant_Currency'].to_s
167
+
168
+ case Sermepa.transaction_from_code(@fields['Ds_Merchant_TransactionType'])
169
+ when :recurring_transaction
170
+ str += @fields['Ds_Merchant_SumTotal']
171
+ end
172
+
173
+ if credentials[:key_type].blank? || credentials[:key_type] == 'sha1_extended'
174
+ str += @fields['Ds_Merchant_TransactionType'].to_s +
175
+ @fields['Ds_Merchant_MerchantURL'].to_s # may be blank!
176
+ end
177
+
178
+ str += credentials[:secret_key]
179
+
180
+ Digest::SHA1.hexdigest(str)
181
+ end
182
+
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,146 @@
1
+ # encoding: utf-8
2
+ module ActiveMerchant #:nodoc:
3
+ module Billing #:nodoc:
4
+ module Integrations #:nodoc:
5
+ module Sermepa
6
+ class Notification < ActiveMerchant::Billing::Integrations::Notification
7
+ include PostsData
8
+
9
+ def complete?
10
+ status == 'Completed'
11
+ end
12
+
13
+ def transaction_id
14
+ params['ds_order']
15
+ end
16
+
17
+ # When was this payment received by the client.
18
+ def received_at
19
+ if params['ds_date']
20
+ (day, month, year) = params['ds_date'].split('/')
21
+ Time.parse("#{year}-#{month}-#{day} #{params['ds_hour']}")
22
+ else
23
+ Time.now # Not provided!
24
+ end
25
+ end
26
+
27
+ # the money amount we received in cents in X.2 format
28
+ def gross
29
+ sprintf("%.2f", params['ds_amount'].to_f / 100)
30
+ end
31
+
32
+ # Was this a test transaction?
33
+ def test?
34
+ false
35
+ end
36
+
37
+ def currency
38
+ Sermepa.currency_from_code( params['ds_currency'] )
39
+ end
40
+
41
+ # Status of transaction. List of possible values:
42
+ # <tt>Completed</tt>
43
+ # <tt>Failed</tt>
44
+ # <tt>Pending</tt>
45
+ def status
46
+ case error_code.to_i
47
+ when 0..99
48
+ 'Completed'
49
+ when 900
50
+ 'Pending'
51
+ else
52
+ 'Failed'
53
+ end
54
+ end
55
+
56
+ def error_code
57
+ params['ds_response']
58
+ end
59
+
60
+ def error_message
61
+ msg = Sermepa.response_code_message(error_code)
62
+ error_code.to_s + ' - ' + (msg.nil? ? 'Operación Aceptada' : msg)
63
+ end
64
+
65
+ def secure_payment?
66
+ params['ds_securepayment'] == '1'
67
+ end
68
+
69
+ # Acknowledge the transaction.
70
+ #
71
+ # Validate the details provided by the gateway by ensuring that the signature
72
+ # matches up with the details provided.
73
+ #
74
+ # Optionally, a set of credentials can be provided that should contain a
75
+ # :secret_key instead of using the global credentials defined in the Sermepa::Helper.
76
+ #
77
+ # Example:
78
+ #
79
+ # def notify
80
+ # notify = Sermepa::Notification.new(request.query_parameters)
81
+ #
82
+ # if notify.acknowledge
83
+ # ... process order ... if notify.complete?
84
+ # else
85
+ # ... log possible hacking attempt ...
86
+ # end
87
+ #
88
+ #
89
+ def acknowledge(credentials = nil)
90
+ return false if params['ds_signature'].blank?
91
+ str =
92
+ params['ds_amount'].to_s +
93
+ params['ds_order'].to_s +
94
+ params['ds_merchantcode'].to_s +
95
+ params['ds_currency'].to_s +
96
+ params['ds_response'].to_s
97
+ if xml?
98
+ str += params['ds_transactiontype'].to_s + params['ds_securepayment'].to_s
99
+ end
100
+
101
+ str += (credentials || Sermepa::Helper.credentials)[:secret_key]
102
+ sig = Digest::SHA1.hexdigest(str)
103
+ sig.upcase == params['ds_signature'].to_s.upcase
104
+ end
105
+
106
+ private
107
+
108
+ def xml?
109
+ !params['code'].blank?
110
+ end
111
+
112
+ # Take the posted data and try to extract the parameters.
113
+ #
114
+ # Posted data can either be a parameters hash, XML string or CGI data string
115
+ # of parameters.
116
+ #
117
+ def parse(post)
118
+ if post.is_a?(Hash)
119
+ post.each { |key, value| params[key.downcase] = value }
120
+ elsif post.to_s =~ /<retornoxml>/i
121
+ # XML source
122
+ self.params = xml_response_to_hash(@raw)
123
+ else
124
+ for line in post.to_s.split('&')
125
+ key, value = *line.scan( %r{^([A-Za-z0-9_.]+)\=(.*)$} ).flatten
126
+ params[key.downcase] = CGI.unescape(value)
127
+ end
128
+ end
129
+ @raw = post.inspect.to_s
130
+ end
131
+
132
+ def xml_response_to_hash(xml)
133
+ result = { }
134
+ doc = Nokogiri::XML(xml)
135
+ doc.css('RETORNOXML OPERACION').children().each do |child|
136
+ result[child.name.downcase] = child.inner_text
137
+ end
138
+ result['code'] = doc.css('RETORNOXML CODIGO').inner_text
139
+ result
140
+ end
141
+
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,10 @@
1
+ module ActiveMerchant #:nodoc:
2
+ module Billing #:nodoc:
3
+ module Integrations #:nodoc:
4
+ module BbvaTpv
5
+ class Return < ActiveMerchant::Billing::Integrations::Return
6
+ end
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+
2
+ require 'active_merchant'
3
+
4
+ module ActiveMerchant
5
+ module Billing
6
+ module Integrations
7
+ autoload :Sermepa, 'active_merchant/billing/integrations/sermepa'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+
2
+ require 'active_merchant_sermepa'
3
+
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require 'rubygems'
5
+
6
+ $:.unshift File.expand_path('../../lib', __FILE__)
7
+
8
+ require 'test/unit'
9
+ require 'money'
10
+ require 'mocha'
11
+ require 'yaml'
12
+ require 'active_merchant_sermepa'
13
+
14
+ require 'action_controller'
15
+ require 'action_view/template'
16
+ begin
17
+ require 'action_dispatch/testing/test_process'
18
+ rescue LoadError
19
+ require 'action_controller/test_process'
20
+ end
21
+ require 'active_merchant/billing/integrations/action_view_helper'
22
+
23
+ ActiveMerchant::Billing::Base.mode = :test
24
+
25
+
26
+ ## Assertions copied from ActiveMerchant. This is lame, they should have them in a seperate file!
27
+
28
+ module ActiveMerchant
29
+ module Assertions
30
+ AssertionClass = RUBY_VERSION > '1.9' ? MiniTest::Assertion : Test::Unit::AssertionFailedError
31
+
32
+ def assert_field(field, value)
33
+ clean_backtrace do
34
+ assert_equal value, @helper.fields[field]
35
+ end
36
+ end
37
+
38
+ # Allows the testing of you to check for negative assertions:
39
+ #
40
+ # # Instead of
41
+ # assert !something_that_is_false
42
+ #
43
+ # # Do this
44
+ # assert_false something_that_should_be_false
45
+ #
46
+ # An optional +msg+ parameter is available to help you debug.
47
+ def assert_false(boolean, message = nil)
48
+ message = build_message message, '<?> is not false or nil.', boolean
49
+
50
+ clean_backtrace do
51
+ assert_block message do
52
+ not boolean
53
+ end
54
+ end
55
+ end
56
+
57
+ # A handy little assertion to check for a successful response:
58
+ #
59
+ # # Instead of
60
+ # assert_success response
61
+ #
62
+ # # DRY that up with
63
+ # assert_success response
64
+ #
65
+ # A message will automatically show the inspection of the response
66
+ # object if things go afoul.
67
+ def assert_success(response)
68
+ clean_backtrace do
69
+ assert response.success?, "Response failed: #{response.inspect}"
70
+ end
71
+ end
72
+
73
+ # The negative of +assert_success+
74
+ def assert_failure(response)
75
+ clean_backtrace do
76
+ assert_false response.success?, "Response expected to fail: #{response.inspect}"
77
+ end
78
+ end
79
+
80
+ def assert_valid(validateable)
81
+ clean_backtrace do
82
+ assert validateable.valid?, "Expected to be valid"
83
+ end
84
+ end
85
+
86
+ def assert_not_valid(validateable)
87
+ clean_backtrace do
88
+ assert_false validateable.valid?, "Expected to not be valid"
89
+ end
90
+ end
91
+
92
+ private
93
+ def clean_backtrace(&block)
94
+ yield
95
+ rescue AssertionClass => e
96
+ path = File.expand_path(__FILE__)
97
+ raise AssertionClass, e.message, e.backtrace.reject { |line| File.expand_path(line) =~ /#{path}/ }
98
+ end
99
+ end
100
+ end
101
+
102
+ Test::Unit::TestCase.class_eval do
103
+ include ActiveMerchant::Assertions
104
+ end
105
+
@@ -0,0 +1,73 @@
1
+ require File.expand_path('../../../../test_helper', __FILE__)
2
+
3
+ class SermepaHelperTest < Test::Unit::TestCase
4
+ include ActiveMerchant::Billing::Integrations
5
+
6
+ def setup
7
+ Sermepa::Helper.credentials = {
8
+ :terminal_id => '9',
9
+ :commercial_id => '999008881',
10
+ :secret_key => 'qwertyasdf0123456789'
11
+ }
12
+ @helper = Sermepa::Helper.new('070803113316', 'Comercio Pruebas', :amount => 825, :currency => 'EUR')
13
+ @helper.description = "Alfombrilla para raton"
14
+ @helper.customer_name = "Sermepa"
15
+ @helper.notify_url = "https://sis-t.sermepa.es:25443/sis/pruebaCom.jsp"
16
+ end
17
+
18
+ def test_credentials_accessible
19
+ assert_instance_of Hash, @helper.credentials
20
+ end
21
+
22
+ def test_credentials_overwritable
23
+ @helper = Sermepa::Helper.new(29292929, 'cody@example.com', :amount => 1235, :currency => 'EUR',
24
+ :credentials => {:terminal_id => 12})
25
+ assert_field 'Ds_Merchant_Terminal', '12'
26
+ end
27
+
28
+ def test_basic_helper_fields
29
+ assert_field 'Ds_Merchant_MerchantCode', '999008881'
30
+ assert_field 'Ds_Merchant_Amount', '825'
31
+ assert_field 'Ds_Merchant_Order', '070803113316'
32
+ assert_field 'Ds_Merchant_ProductDescription', 'Alfombrilla para raton'
33
+ assert_field 'Ds_Merchant_Currency', '978'
34
+ assert_field 'Ds_Merchant_TransactionType', '0'
35
+ assert_field 'Ds_Merchant_MerchantName', 'Comercio Pruebas'
36
+ end
37
+
38
+ def test_unknown_mapping
39
+ assert_nothing_raised do
40
+ @helper.company_address :address => '500 Dwemthy Fox Road'
41
+ end
42
+ end
43
+
44
+ def test_padding_on_order_id
45
+ @helper.order = 101
46
+ assert_field 'Ds_Merchant_Order', "0000101"
47
+ end
48
+
49
+ def test_no_padding_on_valid_order_id
50
+ @helper.order = 1010
51
+ assert_field 'Ds_Merchant_Order', "1010"
52
+ end
53
+
54
+ def test_error_raised_on_invalid_order_id
55
+ assert_raise RuntimeError do
56
+ @helper.order = "A0000000ABC"
57
+ end
58
+ end
59
+
60
+ def test_basic_signing_request
61
+ assert sig = @helper.send(:sign_request)
62
+ assert_equal "ca2bd747d365b4f0a87c670b270cc390b79670ce", sig
63
+ end
64
+
65
+ def test_build_xml_confirmation_request
66
+ # This also tests signing the request for differnet transactions
67
+ data = @helper.send(:build_xml_request)
68
+ assert data =~ /<DS_MERCHANT_TRANSACTIONTYPE>0<\/DS_MERCHANT_TRANSACTIONTYPE>/
69
+ assert data =~ /<DS_MERCHANT_MERCHANTCODE>999008881<\/DS_MERCHANT_MERCHANTCODE>/
70
+ assert data =~ /<DS_MERCHANT_MERCHANTSIGNATURE>ca2bd747d365b4f0a87c670b270cc390b79670ce<\/DS_MERCHANT_MERCHANTSIGNATURE>/
71
+ end
72
+
73
+ end
@@ -0,0 +1,64 @@
1
+ require File.expand_path('../../../../test_helper', __FILE__)
2
+
3
+ class SermepaNotificationTest < Test::Unit::TestCase
4
+ include ActiveMerchant::Billing::Integrations
5
+
6
+ def setup
7
+ Sermepa::Helper.credentials = {
8
+ :terminal_id => '1',
9
+ :commercial_id => '999008881',
10
+ :secret_key => 'qwertyasdf0123456789'
11
+ }
12
+ @sermepa = Sermepa::Notification.new(raw_params_data)
13
+ end
14
+
15
+ def test_accessors
16
+ assert @sermepa.complete?
17
+ assert_equal "Completed", @sermepa.status
18
+ assert_equal "070820124150", @sermepa.transaction_id
19
+ assert_equal "0.45", @sermepa.gross
20
+ assert_equal "EUR", @sermepa.currency
21
+ assert_equal Time.parse("2007-08-20 12:47"), @sermepa.received_at
22
+ end
23
+
24
+ def test_compositions
25
+ assert_equal Money.new(45, 'EUR'), @sermepa.amount
26
+ end
27
+
28
+ def test_respond_to_acknowledge
29
+ assert @sermepa.respond_to?(:acknowledge)
30
+ end
31
+
32
+ # Replace with real successful acknowledgement code
33
+ def test_acknowledgement
34
+ assert @sermepa.acknowledge
35
+ end
36
+
37
+ def test_acknowledgement_with_xml
38
+ # Fake the presence of xml!
39
+ @sermepa.params['code'] = '123'
40
+ @sermepa.params["ds_signature"] = "49A8A907D86FE4763890180061E7907589DBE96A"
41
+ assert @sermepa.acknowledge
42
+ end
43
+
44
+ private
45
+ def raw_params_data
46
+ {
47
+ "Ds_AuthorisationCode" => "004022",
48
+ "Ds_SecurePayment" => "1",
49
+ "Ds_Hour" => "12:47",
50
+ "Ds_MerchantData" => "",
51
+ "Ds_Terminal" => "001",
52
+ "Ds_Card_Country" => "724",
53
+ "Ds_Response" => "0000",
54
+ "Ds_Currency" => "978",
55
+ "Ds_MerchantCode" => "999008881",
56
+ "Ds_ConsumerLanguage" => "1",
57
+ "Ds_TransactionType" => "0",
58
+ "Ds_Signature" => "E2E5A14D690B869183CF3BA36E2B6005BB21F9C5",
59
+ "Ds_Order" => "070820124150",
60
+ "Ds_Amount" => "45",
61
+ "Ds_Date" => "20/08/2007"
62
+ }
63
+ end
64
+ end
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../../../test_helper', __FILE__)
2
+
3
+ class SermepaModuleTest < Test::Unit::TestCase
4
+ include ActiveMerchant::Billing::Integrations
5
+
6
+ def test_notification_method
7
+ assert_instance_of Sermepa::Notification, Sermepa.notification('name=cody')
8
+ end
9
+
10
+ def test_currency_code
11
+ assert_equal '978', Sermepa.currency_code('EUR')
12
+ end
13
+ def test_currency_from_code
14
+ assert_equal 'EUR', Sermepa.currency_from_code('978')
15
+ end
16
+
17
+ def test_language_code
18
+ assert_equal Sermepa.language_code('es'), '001'
19
+ assert_equal Sermepa.language_code('CA'), '003'
20
+ assert_equal Sermepa.language_code(:pt), '009'
21
+ end
22
+ def test_language_from_code
23
+ assert_equal :ca, Sermepa.language_from_code('003')
24
+ end
25
+
26
+ def test_transaction_code
27
+ assert_equal '2', Sermepa.transaction_code(:confirmation)
28
+ end
29
+ def test_transaction_from_code
30
+ assert_equal :confirmation, Sermepa.transaction_from_code(2)
31
+ end
32
+
33
+ def test_response_code_message
34
+ assert_equal nil, Sermepa.response_code_message(23)
35
+ assert_equal nil, Sermepa.response_code_message('23')
36
+ assert_equal "Tarjeta caducada", Sermepa.response_code_message(101)
37
+ end
38
+
39
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_merchant_sermepa
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 1
9
+ version: 0.1.1
10
+ platform: ruby
11
+ authors:
12
+ - Sam Lown
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-02-25 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activemerchant
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 1
30
+ - 9
31
+ - 4
32
+ version: 1.9.4
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: mocha
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ segments:
44
+ - 0
45
+ - 9
46
+ - 10
47
+ version: 0.9.10
48
+ type: :development
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: money
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ~>
57
+ - !ruby/object:Gem::Version
58
+ segments:
59
+ - 3
60
+ - 5
61
+ - 4
62
+ version: 3.5.4
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: actionpack
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ~>
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 3
75
+ - 0
76
+ - 3
77
+ version: 3.0.3
78
+ type: :development
79
+ version_requirements: *id004
80
+ description: Add support to ActiveMerchant for the Sermepa payment gateway by Servired used by many banks in Spain
81
+ email: me@samlown.com
82
+ executables: []
83
+
84
+ extensions: []
85
+
86
+ extra_rdoc_files:
87
+ - MIT-LICENSE
88
+ - CHANGELOG
89
+ - README.rdoc
90
+ files:
91
+ - .gitignore
92
+ - CHANGELOG
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - MIT-LICENSE
96
+ - Manifest
97
+ - README.rdoc
98
+ - Rakefile
99
+ - active_merchant_sermepa.gemspec
100
+ - lib/active_merchant/billing/integrations/sermepa.rb
101
+ - lib/active_merchant/billing/integrations/sermepa/helper.rb
102
+ - lib/active_merchant/billing/integrations/sermepa/notification.rb
103
+ - lib/active_merchant/billing/integrations/sermepa/return.rb
104
+ - lib/active_merchant_sermepa.rb
105
+ - lib/activemerchant_sermepa.rb
106
+ - test/test_helper.rb
107
+ - test/unit/integrations/helpers/sermepa_helper_test.rb
108
+ - test/unit/integrations/notifications/sermepa_notification_test.rb
109
+ - test/unit/integrations/sermepa_module_text.rb
110
+ has_rdoc: true
111
+ homepage: http://github.com/samlown/active_merchant_sermepa
112
+ licenses: []
113
+
114
+ post_install_message:
115
+ rdoc_options: []
116
+
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ none: false
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ segments:
125
+ - 0
126
+ version: "0"
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ segments:
133
+ - 1
134
+ - 3
135
+ - 1
136
+ version: 1.3.1
137
+ requirements: []
138
+
139
+ rubyforge_project:
140
+ rubygems_version: 1.3.7
141
+ signing_key:
142
+ specification_version: 3
143
+ summary: ActiveMerchant support for Servired's Sermepa payment gateway
144
+ test_files: []
145
+