Compare commits

..

10 Commits
3.0 ... master

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
11 changed files with 274 additions and 193 deletions

2
.gitignore vendored
View File

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

3
Gemfile 100644
View File

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

View File

@ -9,50 +9,59 @@ It's a Ruby on Rails port of the connexion kits published by the bank.
## INSTALL ## INSTALL
script/plugin install git://github.com/novelys/paiementcic.git gem 'paiement_cic'
or, in your Gemfile
gem 'paiement_cic', :git => 'git://github.com/novelys/paiementcic.git', :branch => 'gem'
## USAGE ## USAGE
### in environment.rb : ### in an initializer (`config/initializers/paiement_cic.rb`) :
```ruby
PaiementCic.default_config.configure do |config|
# here the hmac key calculated with the js calculator given by CIC # here the hmac key calculated with the js calculator given by CIC
PaiementCic.hmac_key = "########################################" config.hmac_key = "########################################"
# Here the TPE number # Here the TPE number
PaiementCic.tpe = "#######" config.tpe = "#######"
# Here the Merchant name # Here the Merchant name
PaiementCic.societe = "xxxxxxxxxxxxx" 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" # or https://paiement.creditmutuel.fr/test/paiement.cgi You can also specify different attributes when initializing `PaiementCic::TPE` and with `paiement_cic_hidden_fields` helper by passing an hash.
```ruby
### in production.rb : {
hmac_key: "########################################",
PaiementCic.target_url = "https://ssl.paiement.cic-banques.fr/paiement.cgi" # or https://paiement.creditmutuel.fr/paiement.cgi tpe: "#######",
societe: "xxxxxxxxxxxxx",
### in order controller : env: :test,
}
helper :'paiement_cic/form' ```
### in the payment by card view : ### 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)) - 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;" = submit_tag "Accéder au site de la banque", :style => "font-weight: bold;"
= image_tag "reassuring_pictograms.jpg", :alt => "Pictogrammes rassurants", :style => "width: 157px;" = image_tag "reassuring_pictograms.jpg", :alt => "Pictogrammes rassurants", :style => "width: 157px;"
```
### in a controller for call back from the bank : ### in a controller for call back from the bank :
```ruby
class OrderTransactionsController < ApplicationController class OrderTransactionsController < ApplicationController
protect_from_forgery :except => [:bank_callback] protect_from_forgery :except => [:bank_callback]
def bank_callback def bank_callback
if PaiementCic.verify_hmac(params) tpe = PaiementCic::TPE.new
if tpe.verify_hmac(params)
order_transaction = OrderTransaction.find_by_reference params[:reference], :last order_transaction = OrderTransaction.find_by_reference params[:reference], :last
order = order_transaction.order order = order_transaction.order
@ -81,7 +90,7 @@ or, in your Gemfile
order.update_attribute :description, "Document Falsifie." order.update_attribute :description, "Document Falsifie."
order_transaction.update_attribute :success, false order_transaction.update_attribute :success, false
receipt = "1\n#{PaiementCic.mac_string}" receipt = "1\n#{tpe.mac_string}"
end end
render :text => "Pragma: no-cache\nContent-type: text/plain\n\nversion=2\ncdr=#{receipt}" render :text => "Pragma: no-cache\nContent-type: text/plain\n\nversion=2\ncdr=#{receipt}"
end end
@ -89,17 +98,16 @@ or, in your Gemfile
def bank_ok def bank_ok
@order_transaction = OrderTransaction.find params[:id] @order_transaction = OrderTransaction.find params[:id]
@order = @order_transaction.order @order = @order_transaction.order
@order.pay!
end end
def bank_err def bank_err
order_transaction = OrderTransaction.find params[:id] order_transaction = OrderTransaction.find params[:id]
order = order_transaction.order order = order_transaction.order
order.cancel!
end end
end end
```
## License ## 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/form_helper"

View File

@ -1,8 +1,10 @@
require 'paiement_cic/config'
require 'paiement_cic/tpe'
require 'paiement_cic/railtie' if defined?(Rails)
require 'digest/sha1' require 'digest/sha1'
require 'openssl'
class String class String
def ^(other) def ^(other)
raise ArgumentError, "Can't bitwise-XOR a String with a non-String" \ raise ArgumentError, "Can't bitwise-XOR a String with a non-String" \
unless other.kind_of? String unless other.kind_of? String
@ -13,68 +15,28 @@ class String
end end
end end
class PaiementCic module PaiementCic
autoload :FormHelper, "paiement_cic/form_helper" API_VERSION = "3.0"
DATE_FORMAT = "%d/%m/%Y:%H:%M:%S"
@@version = "3.0" # clé extraite grâce à extract2HmacSha1.html fourni par le Crédit Mutuel END_POINTS = {
cattr_accessor :version 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
@@hmac_key = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ" # clé extraite grâce à extract2HmacSha1.html fourni par le Crédit Mutuel def self.default_config
cattr_accessor :hmac_key @@default_config ||= PaiementCic::Config.new
@@target_url = "https://paiement.creditmutuel.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
@@url_ok = ""
cattr_accessor :url_ok
def self.date_format
"%d/%m/%Y:%H:%M:%S"
end end
def self.config(amount_in_cents, reference) def self.hmac_sha1(key, data)
oa = ActiveSupport::OrderedHash.new
oa["version"] = "3.0"
oa["TPE"] = tpe
oa["date"] = Time.now.strftime(date_format)
oa["montant"] = ("%.2f" % amount_in_cents) + "EUR"
oa["reference"] = reference
oa["texte-libre"] = ""
oa["lgue"] = "FR"
oa["societe"] = societe
oa["mail"] = ""
oa
end
def self.mac_string params
hmac_key = PaiementCic.new
mac_string = [hmac_key.tpe, params["date"], params['montant'], params['reference'], params['texte-libre'], hmac_key.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 self.verify_hmac params
hmac_key = PaiementCic.new
mac_string = [hmac_key.tpe, params["date"], params['montant'], params['reference'], params['texte-libre'], hmac_key.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('*') + "*"
hmac_key.valid_hmac?(mac_string, params['MAC'])
end
# Check if the HMAC matches the HMAC of the data string
def valid_hmac?(mac_string, sent_mac)
computeHMACSHA1(mac_string) == sent_mac.downcase
end
# Return the HMAC for a data string
def computeHMACSHA1(data)
hmac_sha1(usable_key(self), data).downcase
end
def hmac_sha1(key, data)
length = 64 length = 64
if (key.length > length) if (key.length > length)
@ -82,33 +44,13 @@ class PaiementCic
end end
key = key.ljust(length, 0.chr) key = key.ljust(length, 0.chr)
ipad = ''.ljust(length, 54.chr)
opad = ''.ljust(length, 92.chr)
k_ipad = key ^ ipad k_ipad = key ^ ''.ljust(length, 54.chr)
k_opad = key ^ opad k_opad = key ^ ''.ljust(length, 92.chr)
#Digest::SHA1.hexdigest(k_opad + [Digest::SHA1.hexdigest(k_ipad + sData)].pack("H*")) Digest::SHA1.hexdigest(k_opad + [Digest::SHA1.hexdigest(k_ipad + data)].pack("H*"))
OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("sha1"), key, data)
end end
private class UnknownBankError < Exception; end
# Return the key to be used in the hmac function class UnknownEnvError < Exception; end
def usable_key(payement)
hex_string_key = payement.hmac_key[0..37]
hex_final = payement.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,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

@ -1,28 +1,23 @@
## refactor this
module PaiementCic::FormHelper module PaiementCic::FormHelper
def paiement_cic_hidden_fields(order, price, order_transaction, options = {}) def paiement_cic_hidden_fields(reference, price, options = {})
oa = PaiementCic.config(price, order_transaction.reference) oMac = PaiementCic::TPE.new(options)
oa = oMac.attributes(reference, price, options)
oMac = PaiementCic.new chaineMAC = oMac.compute_hmac_sha1(oa.values.join('*'))
sDate = Time.now.strftime("%d/%m/%Y:%H:%M:%S")
chaine = [oMac.tpe, sDate, oa["montant"], oa["reference"].to_s, oa["texte-libre"], oMac.version, "FR", oMac.societe, "", "", "", "", "", "", "", "", "", "", ""].join("*")
chaineMAC = oMac.computeHMACSHA1(chaine)
html = ' url_retour = options[:url_retour]
<input type="hidden" name="version" id="version" value="' + oa["version"] + '" /> url_retour_ok = options[:url_retour_ok]
<input type="hidden" name="TPE" id="TPE" value="' + oa["TPE"] + '" /> url_retour_err = options[:url_retour_err]
<input type="hidden" name="date" id="date" value="' + oa["date"] + '" />
<input type="hidden" name="montant" id="montant" value="' + oa["montant"] + '" />
<input type="hidden" name="reference" id="reference" value="' + oa["reference"].to_s + '" />
<input type="hidden" name="MAC" id="MAC" value="' + chaineMAC + '" />
<input type="hidden" name="url_retour" id="url_retour" value="' + bank_callback_order_transactions_url + '" />
<input type="hidden" name="url_retour_ok" id="url_retour_ok" value="' + bank_ok_order_transaction_url(order) + '" />
<input type="hidden" name="url_retour_err" id="url_retour_err" value="' + bank_err_order_transaction_url(order) + '" />
<input type="hidden" name="lgue" id="lgue" value="' + oa["lgue"] + '" />
<input type="hidden" name="societe" id="societe" value="' + oa["societe"] + '" />
<input type="hidden" name="texte-libre" id="texte-libre" value="' + oa["texte-libre"] + '" />
<input type="hidden" name="mail" id="mail" value="''" />'
html.respond_to?(:html_safe) ? html.html_safe : html 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
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

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

View File

@ -1,12 +1,13 @@
# encoding: utf-8 # encoding: utf-8
$:.push File.expand_path("../lib", __FILE__) lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require "paiement_cic/version" require "paiement_cic/version"
Gem::Specification.new do |s| Gem::Specification.new do |s|
s.name = "paiement_cic" s.name = "paiement_cic"
s.version = PaiementCic::VERSION s.version = PaiementCic::VERSION
s.platform = Gem::Platform::RUBY s.platform = Gem::Platform::RUBY
s.authors = ["Novelys Team"] s.authors = ["Novelys Team", "La Fourmi Immo"]
s.homepage = "https://github.com/novelys/paiementcic" s.homepage = "https://github.com/novelys/paiementcic"
s.summary = %q{CIC / Crédit Mutuel credit card payment toolbox} 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.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.}