Compare commits

...

19 Commits

Author SHA1 Message Date
Guillaume DOTT e77eb75f07 Move Config attributes to a separate class
This allows to modify the target URL, the environment or any attribute
for a specific TPE or use default config.
2014-04-09 14:38:27 +02:00
Guillaume DOTT fd565f3e6f Update README 2013-06-11 14:12:57 +02:00
Guillaume DOTT 7b428b5b61 Add predefined URLs for CIC and CM and both environments 2013-06-11 11:51:22 +02:00
Guillaume DOTT 93d0c9262d Refactor and improve code
Clean code to improve readability, allow to use different TPE in the
same application, add parameters like mail.
2013-06-05 15:33:46 +02:00
Guillaume DOTT 16a5ad5930 Remove trailing whitespaces and tabs 2013-06-05 10:28:56 +02:00
David Bourguignon 442be992a0 change readme 2012-01-30 13:55:44 +01:00
David Bourguignon 5cf5026e07 bump version 2012-01-30 11:28:14 +01:00
David Bourguignon cf9df67cd9 Merge branch 'master' of github.com:novelys/paiementcic 2012-01-30 11:26:41 +01:00
David Bourguignon 910b448448 use options hash for return urls 2012-01-30 11:26:07 +01:00
David Bourguignon 0e61bba15a Merge pull request #3 from bgarret/ruby-1.9.2
Ruby 1.9 support
2012-01-30 11:25:54 +01:00
David Bourguignon 52704a5aba Merge pull request #3 from bgarret/ruby-1.9.2
Ruby 1.9 support
2012-01-30 00:39:50 -08:00
David Bourguignon d07822c628 still need support for Rails 2.3 2012-01-30 09:38:45 +01:00
David Bourguignon d4bc57df1a Merge branch 'rails3' of https://github.com/bgarret/paiementcic into bgarret-rails3 2012-01-30 09:36:43 +01:00
David Bourguignon 57fa761418 Merge pull request #1 from bgarret/gem
Gemify the plugin
2012-01-30 00:15:29 -08:00
Benoit Garret eb8033c57c Rails 3 treats all strings as unsafe by default 2011-06-24 17:50:57 +02:00
Benoit Garret 948ad95235 Explicitly convert chars to integers 2011-06-24 17:47:54 +02:00
Benoit Garret 7be9c5f2d8 Gemify the plugin 2011-06-23 16:38:31 +02:00
Benoit Garret cca9e3a7da Move lib/paiement_cic_helper to lib/paiement_cic/form_helper 2011-06-23 16:04:13 +02:00
Toutie d9c53853ed modifications to support version 3 2009-12-21 11:24:45 +01:00
13 changed files with 361 additions and 154 deletions

3
.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
Gemfile.lock
*.gem
.*.swp

3
Gemfile 100644
View File

@ -0,0 +1,3 @@
source 'https://rubygems.org'
gemspec

View File

@ -18,3 +18,66 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#==============================================================================
#
# "Open source" kit for P@iement CM-CIC(TM).
# Integration sample in a merchant site for Ruby
#
# Author : Euro-Information/e-Commerce (contact: centrecom@e-i.com)
# Version : 1.0
# Date : 01/01/2009
#
# Copyright: (c) 2009 Euro-Information. All rights reserved.
#
#==============================================================================
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
- Redistributions of source code must retain the above copyright
notice and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice and the following disclaimer in the documentation and/or
other materials provided with the distribution.
- Neither the name of Euro-Information nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Note: Euro-Information does not provide person-to-person technical
support for tryout of CM-CIC P@iement examples. We do however
welcome your feedback which can be sent to <centrecom@e-i.com>.
#------------------------------------------------------------------------------
This software uses RSA Data Security, Inc. MD5 Message-Digest Algorithm.
License to copy and use this software is granted provided that it is
identified as the "RSA Data Security, Inc. MD5 Message-Digest
Algorithm" in all material mentioning or referencing this software or
this function.
License is also granted to make and use derivative works provided that
such works are identified as "derived from the RSA Data Security,
Inc. MD5 Message-Digest Algorithm" in all material mentioning or
referencing the derived work.
RSA Data Security, Inc. makes no representations concerning either the
merchantability of this software or the suitability of this software
for any particular purpose. It is provided "as is" without express or
implied warranty of any kind.
These notices must be retained in any copies of any part of this
documentation and/or software.
#==============================================================================

View File

@ -1,6 +1,6 @@
# Paiement CIC
Paiement CIC is a plugin to ease credit card payment with the CIC / Crédit Mutuel banks system.
Paiement CIC is a plugin to ease credit card payment with the CIC / Crédit Mutuel banks system version 3.0.
It's a Ruby on Rails port of the connexion kits published by the bank.
* The Plugin [site](http://github.com/novelys/cicpayment)
@ -9,93 +9,105 @@ It's a Ruby on Rails port of the connexion kits published by the bank.
## INSTALL
script/plugin install git://github.com/novelys/paiementcic.git
gem 'paiement_cic'
## USAGE
### in environment.rb :
### in an initializer (`config/initializers/paiement_cic.rb`) :
# here the hmac key calculated with the js calculator given by CIC
PaiementCic.hmac_key = "########################################"
# Here the TPE number
PaiementCic.tpe = "#######"
# Here the Merchant name
PaiementCic.societe = "xxxxxxxxxxxxx"
```ruby
PaiementCic.default_config.configure do |config|
# here the hmac key calculated with the js calculator given by CIC
config.hmac_key = "########################################"
# Here the TPE number
config.tpe = "#######"
# Here the Merchant name
config.societe = "xxxxxxxxxxxxx"
### in development.rb :
# You can specify the target URL
config.target_url = "https://ssl.paiement.cic-banques.fr/test/paiement.cgi"
# Or used predefined ones by specifying environment and bank
config.bank = :cm # or :cic
config.env = :test # or :production
end
```
PaiementCic.target_url = "https://ssl.paiement.cic-banques.fr/test/paiement.cgi"
### in production.rb :
PaiementCic.target_url = "https://ssl.paiement.cic-banques.fr/paiement.cgi"
### in order controller :
helper :paiement_cic
You can also specify different attributes when initializing `PaiementCic::TPE` and with `paiement_cic_hidden_fields` helper by passing an hash.
```ruby
{
hmac_key: "########################################",
tpe: "#######",
societe: "xxxxxxxxxxxxx",
env: :test,
}
```
### in the payment by card view :
- form_tag PaiementCic.target_url do
= paiement_cic_hidden_fields(@order, @order_transaction, :url_retour => edit_order_url(order), :url_retour_ok => bank_ok_order_transaction_url(order_transaction), :url_retour_err => bank_err_order_transaction_url(order_transaction))
= submit_tag "Accéder au site de la banque", :style => "font-weight: bold;"
= image_tag "reassuring_pictograms.jpg", :alt => "Pictogrammes rassurants", :style => "width: 157px;"
```
- form_tag PaiementCic.default_config.target_url do
= paiement_cic_hidden_fields(@order.reference, @order.amount, mail: 'email@example.com', url_retour: edit_order_url(order), url_retour_ok: bank_ok_order_transaction_url(order_transaction), url_retour_err: bank_err_order_transaction_url(order_transaction))
= submit_tag "Accéder au site de la banque", :style => "font-weight: bold;"
= image_tag "reassuring_pictograms.jpg", :alt => "Pictogrammes rassurants", :style => "width: 157px;"
```
### in a controller for call back from the bank :
class OrderTransactionsController < ApplicationController
```ruby
class OrderTransactionsController < ApplicationController
protect_from_forgery :except => [:bank_callback]
protect_from_forgery :except => [:bank_callback]
def bank_callback
if PaiementCic.verify_hmac(params)
order_transaction = OrderTransaction.find_by_reference params[:reference], :last
order = order_transaction.order
def bank_callback
tpe = PaiementCic::TPE.new
code_retour = params['code-retour']
if tpe.verify_hmac(params)
order_transaction = OrderTransaction.find_by_reference params[:reference], :last
order = order_transaction.order
if code_retour == "Annulation"
order.cancel!
order.update_attribute :description, "Paiement refusé par la banque."
code_retour = params['code-retour']
elsif code_retour == "payetest"
order.pay!
order.update_attribute :description, "TEST accepté par la banque."
order_transaction.update_attribute :test, true
elsif code_retour == "paiement"
order.pay!
order.update_attribute :description, "Paiement accepté par la banque."
order_transaction.update_attribute :test, false
end
order_transaction.update_attribute :success, true
receipt = "OK"
else
order.transaction_declined!
order.update_attribute :description, "Document Falsifie."
order_transaction.update_attribute :success, false
receipt = "Document Falsifie"
end
render :text => "Version: 1\n#{receipt}\n"
end
def bank_ok
@order_transaction = OrderTransaction.find params[:id]
@order = @order_transaction.order
@order.pay!
end
def bank_err
order_transaction = OrderTransaction.find params[:id]
order = order_transaction.order
if code_retour == "Annulation"
order.cancel!
order.update_attribute :description, "Paiement refusé par la banque."
elsif code_retour == "payetest"
order.pay!
order.update_attribute :description, "TEST accepté par la banque."
order_transaction.update_attribute :test, true
elsif code_retour == "paiement"
order.pay!
order.update_attribute :description, "Paiement accepté par la banque."
order_transaction.update_attribute :test, false
end
order_transaction.update_attribute :success, true
receipt = "0"
else
order.transaction_declined!
order.update_attribute :description, "Document Falsifie."
order_transaction.update_attribute :success, false
receipt = "1\n#{tpe.mac_string}"
end
render :text => "Pragma: no-cache\nContent-type: text/plain\n\nversion=2\ncdr=#{receipt}"
end
def bank_ok
@order_transaction = OrderTransaction.find params[:id]
@order = @order_transaction.order
end
def bank_err
order_transaction = OrderTransaction.find params[:id]
order = order_transaction.order
end
end
```
## License
Copyright (c) 2008-2009 Novelys Team, released under the MIT license
Copyright (c) 2008-2012 Novelys Team, released under the MIT license

View File

@ -1,2 +0,0 @@
require "paiement_cic"
require "paiement_cic_helper"

View File

@ -1,86 +1,56 @@
require 'paiement_cic/config'
require 'paiement_cic/tpe'
require 'paiement_cic/railtie' if defined?(Rails)
require 'digest/sha1'
require 'openssl'
class String
def ^(other)
raise ArgumentError, "Can't bitwise-XOR a String with a non-String" \
unless other.kind_of? String
raise ArgumentError, "Can't bitwise-XOR strings of different length" \
unless self.length == other.length
result = (0..self.length-1).collect { |i| self[i] ^ other[i] }
result = (0..self.length-1).collect { |i| self[i].ord ^ other[i].ord }
result.pack("C*")
end
end
class PaiementCic
@@hmac_key = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" # clé extraite grâce à extract2HmacSha1.html fourni par le Crédit Mutuel
cattr_accessor :hmac_key
@@target_url = "https://ssl.paiement.cic-banques.fr/test/paiement.cgi" # "https://ssl.paiement.cic-banques.fr/paiement.cgi"
cattr_accessor :target_url
@@tpe = "123456"
cattr_accessor :tpe
@@societe = "masociete"
cattr_accessor :societe
def self.date_format
"%d/%m/%Y:%H:%M:%S"
end
module PaiementCic
API_VERSION = "3.0"
DATE_FORMAT = "%d/%m/%Y:%H:%M:%S"
def self.config(amount_in_cents, reference)
oa = ActiveSupport::OrderedHash.new
oa["TPE"] = tpe
oa["date"] = Time.now.strftime(date_format)
oa["montant"] = ("%.2f" % (amount_in_cents/100.0)) + "EUR"
oa["reference"] = reference
oa["texte-libre"] = ""
oa["version"] = "1.2open"
oa["lgue"] = "FR"
oa["societe"] = societe
oa
end
END_POINTS = {
cic: {
production: 'https://ssl.paiement.cic-banques.fr/paiement.cgi',
test: 'https://ssl.paiement.cic-banques.fr/test/paiement.cgi'
},
cm: {
production: 'https://paiement.creditmutuel.fr/paiement.cgi',
test: 'https://paiement.creditmutuel.fr/test/paiement.cgi'
}
}
DEFAULT_BANK = :cm
DEFAULT_ENV = :test
def self.calculate_hmac(ordered_hash)
data = ordered_hash.values.join("*") + "*"
hmac(data)
end
def self.verify_hmac params
tpe = params[:TPE]
date = params[:date]
montant = params[:montant]
reference = params[:reference]
mac = params[:MAC].downcase
texte_libre = params['texte-libre']
code_retour = params['code-retour']
retour_plus = params['retourPLUS']
version = "1.2open"
data = retour_plus + [tpe, date, montant, reference, texte_libre, version, code_retour].join("+") + "+"
mac == hmac(data)
end
def self.hmac(data)
pass = "";
k1 = [Digest::SHA1.hexdigest(pass)].pack("H*");
l1 = k1.length
k2 = [hmac_key].pack("H*")
l2 = k2.length
if (l1 > l2)
k2 = k2.ljust(l1, 0.chr)
elsif (l2 > l1)
k1 = k1.ljust(l2, 0.chr)
end
xor_res = k1 ^ k2
hmac_sha1(xor_res, data).downcase
def self.default_config
@@default_config ||= PaiementCic::Config.new
end
def self.hmac_sha1(key, data)
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("sha1"), key, data)
length = 64
if (key.length > length)
key = [Digest::SHA1.hexdigest(key)].pack("H*")
end
key = key.ljust(length, 0.chr)
k_ipad = key ^ ''.ljust(length, 54.chr)
k_opad = key ^ ''.ljust(length, 92.chr)
Digest::SHA1.hexdigest(k_opad + [Digest::SHA1.hexdigest(k_ipad + data)].pack("H*"))
end
end
class UnknownBankError < Exception; end
class UnknownEnvError < Exception; end
end

View File

@ -0,0 +1,44 @@
module PaiementCic
class Config
attr_accessor :hmac_key, :tpe, :societe
attr_writer :target_url
def initialize(attributes = {}, &block)
if block_given?
configure(&block)
else
attributes.each do |name, value|
setter = "#{name}="
next unless respond_to?(setter)
send(setter, value)
end
end
end
def configure(&block)
yield self
end
def bank
@bank || DEFAULT_BANK
end
def bank=(value)
raise UnknownBankError unless END_POINTS.keys.include?(value.to_sym)
@bank = value
end
def env
@env || DEFAULT_ENV
end
def env=(value)
raise UnknownEnvError unless END_POINTS.first.last.include?(value.to_sym)
@env = value
end
def target_url
@target_url || END_POINTS[self.bank][self.env]
end
end
end

View File

@ -0,0 +1,23 @@
module PaiementCic::FormHelper
def paiement_cic_hidden_fields(reference, price, options = {})
oMac = PaiementCic::TPE.new(options)
oa = oMac.attributes(reference, price, options)
chaineMAC = oMac.compute_hmac_sha1(oa.values.join('*'))
url_retour = options[:url_retour]
url_retour_ok = options[:url_retour_ok]
url_retour_err = options[:url_retour_err]
html = hidden_field_tag('MAC', chaineMAC)
html << hidden_field_tag('url_retour', url_retour)
html << hidden_field_tag('url_retour_ok', url_retour_ok)
html << hidden_field_tag('url_retour_err', url_retour_err)
oa.each do |k,v|
html << hidden_field_tag(k, v) unless v.empty?
end
html
end
end

View File

@ -0,0 +1,9 @@
require 'paiement_cic/form_helper'
module PaiementCic
class Railtie < Rails::Railtie
initializer "paiement_cic.form_helpers" do
ActionView::Base.send :include, FormHelper
end
end
end

View File

@ -0,0 +1,79 @@
module PaiementCic
class TPE
attr_accessor :config
def initialize(options = nil)
self.config = options.nil? ? PaiementCic.default_config :
PaiementCic::Config.new(options)
end
def attributes(reference, amount_in_cents, options = {})
{
'TPE' => config.tpe,
'date' => Time.now.strftime(PaiementCic::DATE_FORMAT),
'montant' => ("%.2f" % amount_in_cents) + "EUR",
'reference' => reference.to_s,
'texte-libre' => '',
'version' => PaiementCic::API_VERSION,
'lgue' => 'FR',
'societe' => config.societe,
'mail' => options[:mail].to_s,
'nbrech' => options[:nbrech].to_s,
'dateech1' => options[:dateech1].to_s,
'montantech1' => options[:montantech1].to_s,
'dateech2' => options[:dateech2].to_s,
'montantech2' => options[:montantech2].to_s,
'dateech3' => options[:dateech3].to_s,
'montantech3' => options[:montantech3].to_s,
'dateech4' => options[:dateech4].to_s,
'montantech4' => options[:montantech4].to_s,
'options' => options[:options].to_s
}
end
def mac_string params
[
config.tpe, params['date'], params['montant'], params['reference'], params['texte-libre'],
PaiementCic::API_VERSION, params['code-retour'], params['cvx'], params['vld'], params['brand'],
params['status3ds'], params['numauto'], params['motifrefus'], params['originecb'],
params['bincb'], params['hpancb'], params['ipclient'], params['originetr'],
params['veres'], params['pares']
].join('*') + "*"
end
def verify_hmac params
params.has_key?('MAC') && valid_hmac?(mac_string(params), params['MAC'])
end
# Check if the HMAC matches the HMAC of the data string
def valid_hmac?(mac_string, sent_mac)
compute_hmac_sha1(mac_string) == sent_mac.downcase
end
# Return the HMAC for a data string
def compute_hmac_sha1(data)
PaiementCic.hmac_sha1(usable_key, data).downcase
end
alias_method :computeHMACSHA1, :compute_hmac_sha1
private
# Return the key to be used in the hmac function
def usable_key
hex_string_key = config.hmac_key[0..37]
hex_final = config.hmac_key[38..40] + "00";
cca0 = hex_final[0].ord
if cca0 > 70 && cca0 < 97
hex_string_key += (cca0 - 23).chr + hex_final[1..2]
elsif hex_final[1..2] == "M"
hex_string_key += hex_final[0..1] + "0"
else
hex_string_key += hex_final[0..2]
end
[hex_string_key].pack("H*")
end
end
end

View File

@ -0,0 +1,3 @@
module PaiementCic
VERSION = "0.2"
end

View File

@ -1,17 +0,0 @@
module PaiementCicHelper
def paiement_cic_hidden_fields(order, order_transaction, options = {})
oa = PaiementCic.config(order.amount, order_transaction.reference)
hsh = oa.dup
hsh["url_retour"] = options[:url_retour] || edit_order_url(order)
hsh["url_retour_ok"] = options[:url_retour_ok] || bank_ok_order_transaction_url(order_transaction)
hsh["url_retour_err"] = options[:url_retour_err] || bank_err_order_transaction_url(order_transaction)
hsh["MAC"] = PaiementCic.calculate_hmac(oa)
res = "\n"
hsh.each{|key, value|
res << hidden_field_tag(key, value) << "\n"
}
res
end
end

View File

@ -0,0 +1,17 @@
# encoding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "paiement_cic/version"
Gem::Specification.new do |s|
s.name = "paiement_cic"
s.version = PaiementCic::VERSION
s.platform = Gem::Platform::RUBY
s.authors = ["Novelys Team", "La Fourmi Immo"]
s.homepage = "https://github.com/novelys/paiementcic"
s.summary = %q{CIC / Crédit Mutuel credit card payment toolbox}
s.description = %q{Paiement CIC is a gem to ease credit card payment with the CIC / Crédit Mutuel banks system. It's a Ruby on Rails port of the connexion kits published by the bank.}
s.files = `git ls-files`.split("\n")
s.require_paths = ["lib"]
end