Skip to content
This repository has been archived by the owner on Mar 3, 2022. It is now read-only.

Commit

Permalink
Added support for Ogone DirectLink gateway (with unit and remote tests).
Browse files Browse the repository at this point in the history
  • Loading branch information
Nicolas Jacobeus authored and Jesse Storimer committed Jul 30, 2009
1 parent 65a878b commit 7efce06
Show file tree
Hide file tree
Showing 4 changed files with 616 additions and 0 deletions.
244 changes: 244 additions & 0 deletions lib/active_merchant/billing/gateways/ogone.rb
@@ -0,0 +1,244 @@
require 'rexml/document'

module ActiveMerchant #:nodoc:
module Billing #:nodoc:
# = Ogone DirectLink Gateway
#
# DirectLink is the API version of the Ogone Payment Platform. It allows server to server
# communication between Ogone systems and your e-commerce website.
#
# This implementation follows the specification provided in the DirectLink integration
# guide version 2.4 (December 2008), available here:
# https://secure.ogone.com/ncol/Ogone_DirectLink_EN.pdf
#
# It also features aliases, which allow to store/unstore credit cards, as specified in
# the Alias Manager Option guide version 2.2 available here:
# https://secure.ogone.com/ncol/Ogone_Alias_EN.pdf
#
# It was last tested on Release 04.79 of Ogone e-Commerce (dated 11/02/2009).
#
# For any questions or comments, please contact Nicolas Jacobeus (nj@belighted.com).
#
# == Example use:
#
# gateway = ActiveMerchant::Billing::OgoneGateway.new(
# :login => "my_ogone_psp_id",
# :user => "my_ogone_user_id",
# :password => "my_ogone_pswd",
# :signature => "my_ogone_sha1_signature" # extra security, only if you configured your Ogone environment so
# )
#
# # set up credit card obj as in main ActiveMerchant example
# creditcard = ActiveMerchant::Billing::CreditCard.new(
# :type => 'visa',
# :number => '4242424242424242',
# :month => 8,
# :year => 2009,
# :first_name => 'Bob',
# :last_name => 'Bobsen'
# )
#
# # run request
# response = gateway.purchase(1000, creditcard, :order_id => "1") # charge 10 EUR
#
# puts response.success? # Check whether the transaction was successful
# puts response.message # Retrieve the message returned by Ogone
# puts response.authorization # Retrieve the unique transaction ID returned by Ogone
#
# To use the alias feature, simply add :alias in the options hash:
#
# gateway.purchase(1000, creditcard, :order_id => "1", :alias => "myawesomecustomer") # associates the alias to that creditcard
# gateway.purchase(2000, nil, :order_id => "2", :alias => "myawesomecustomer") # don't need to know the creditcard for subsequent orders
#
class OgoneGateway < Gateway

URLS = {
:test => { :order => 'https://secure.ogone.com/ncol/test/orderdirect.asp',
:maintenance => 'https://secure.ogone.com/ncol/test/maintenancedirect.asp' },
:production => { :order => 'https://secure.ogone.com/ncol/prod/orderdirect.asp',
:maintenance => 'https://secure.ogone.com/ncol/prod/maintenancedirect.asp' }
}

CVV_MAPPING = { 'OK' => 'M',
'KO' => 'N',
'NO' => 'P' }

AVS_MAPPING = { 'OK' => 'M',
'KO' => 'N',
'NO' => 'R' }

self.supported_countries = ['BE', 'DE', 'FR', 'NL', 'AT', 'CH']
self.supported_cardtypes = [:visa, :master, :american_express]
self.homepage_url = 'http://www.ogone.com/'
self.display_name = 'Ogone'
self.default_currency = 'EUR'

def initialize(options = {})
requires!(options, :login, :user, :password)
@options = options
super
end

# Verify and reserve the specified amount on the account, without actually doing the transaction.
def authorize(money, creditcard, options = {})
post = {}
add_invoice(post, options)
add_alias(post, options[:alias])
add_creditcard(post, creditcard)
add_address(post, creditcard, options)
add_customer_data(post, options)
add_money(post, money, options)
commit('RES', post)
end

# Verify and transfer the specified amount.
def purchase(money, creditcard, options = {})
post = {}
add_invoice(post, options)
add_alias(post, options[:alias])
add_creditcard(post, creditcard)
add_address(post, creditcard, options)
add_customer_data(post, options)
add_money(post, money, options)
commit('SAL', post)
end

# Complete a previously authorized transaction.
def capture(money, authorization, options = {})
post = {}
add_authorization(post, authorization)
add_invoice(post, options)
add_customer_data(post, options)
add_money(post, money, options)
commit('SAL', post)
end

# Cancels a previously authorized transaction.
def void(identification, options = {})
post = {}
add_authorization(post, identification)
commit('DES', post)
end

# Credit the specified account by a specific amount.
def credit(money, identification_or_credit_card, options = {})
post = {}
if identification_or_credit_card.is_a?(String)
# Referenced credit: refund of a settled transaction
add_authorization(post, identification_or_credit_card)
else # must be a credit card
# Non-referenced credit: acts like a reverse purchase
add_invoice(post, options)
add_alias(post, options[:alias])
add_creditcard(post, identification_or_credit_card)
add_address(post, identification_or_credit_card, options)
add_customer_data(post, options)
end
add_money(post, money, options)
commit('RFD', post)
end

# Recurring payment is not supported yet.
def recurring(money, creditcard, options = {})
raise StandardError, "Not supported"
end

# Store is not supported as Ogone doesn't seem to allow this operation on its own.
# You can though store and unstore credit card numbers through the alias parameter.
def store(creditcard, options = {})
raise StandardError, "Not supported"
end

# Unstore is not supported as Ogone doesn't seem to allow this operation on its own.
# You can though store and unstore credit card numbers through the alias parameter.
def unstore(identification, options = {})
raise StandardError, "Not supported"
end

private

# Specific data to add to the hash ===============================================================

def add_alias(post, _alias)
add_pair post, 'ALIAS', _alias
end

def add_authorization(post, authorization)
add_pair post, 'PAYID', authorization
end

def add_money(post, money, options)
add_pair post, 'currency', options[:currency] || currency(money)
add_pair post, 'amount', money
end

def add_customer_data(post, options)
add_pair post, 'EMAIL', options[:email]
add_pair post, 'REMOTE_ADDR', options[:ip]
end

def add_address(post, creditcard, options)
return unless options[:billing_address]
add_pair post, 'Owneraddress', options[:billing_address][:address1]
add_pair post, 'OwnerZip', options[:billing_address][:zip]
add_pair post, 'ownertown', options[:billing_address][:city]
add_pair post, 'ownercty', options[:billing_address][:country]
add_pair post, 'ownertelno', options[:billing_address][:phone]
end

def add_invoice(post, options)
add_pair post, 'orderID', options[:order_id]
add_pair post, 'COM', options[:description]
end

def add_creditcard(post, creditcard)
return unless (creditcard and creditcard.valid?)
add_pair post, 'CN', "#{creditcard.first_name} #{creditcard.last_name}"
add_pair post, 'CARDNO', creditcard.number
add_pair post, 'ED', "%02d%02s" % [creditcard.month, creditcard.year.to_s[-2..-1]]
add_pair post, 'CVC', creditcard.verification_value
add_pair post, 'BRAND', creditcard.type.upcase
end

# Backend methods ================================================================================

def parse(body)
xml = REXML::Document.new(body)
xml.root.attributes
end

def commit(action, parameters)
add_pair parameters, 'PSPID', @options[:login]
add_pair parameters, 'USERID', @options[:user]
add_pair parameters, 'PSWD', @options[:password]
url = URLS[test? ? :test : production][parameters['PAYID'] ? :maintenance : :order ]
response = parse(ssl_post(url, post_data(action, parameters)))
success = response["NCERROR"]=="0"
message = message_from(response)
options = { :authorization => response["PAYID"],
:test => test?,
:avs_result => { :code => AVS_MAPPING[response["AAVCheck"]] },
:cvv_result => CVV_MAPPING[response["CVCCheck"]] }
Response.new(success, message, response, options)
end

def message_from(response)
response["NCERRORPLUS"]
end

def post_data(action, parameters = {})
add_pair parameters, 'Operation' , action
if @options[:signature] # the user wants a SHA-1 signature
string = ['orderID','amount','currency','CARDNO','PSPID','Operation','ALIAS'].map{|s|parameters[s]}.join + @options[:signature]
add_pair parameters, 'SHASign' , Digest::SHA1.hexdigest(string)
end
parameters.collect { |key, value| "#{key}=#{CGI.escape(value.to_s)}" }.join("&")
end

def add_pair(post, key, value, options = {})
post[key] = value if !value.blank? || options[:required]
end

end
end
end
6 changes: 6 additions & 0 deletions test/fixtures.yml
Expand Up @@ -95,6 +95,12 @@ net_registry:
login: X
password: Y

ogone:
login: LOGIN
user: USER
password: PASSWORD
signature: SIGNATURE

payflow:
login: LOGIN
password: PASSWORD
Expand Down
101 changes: 101 additions & 0 deletions test/remote/gateways/remote_ogone_test.rb
@@ -0,0 +1,101 @@
require File.dirname(__FILE__) + '/../../test_helper'

class RemoteOgoneTest < Test::Unit::TestCase

def setup
@gateway = OgoneGateway.new(fixtures(:ogone))
@amount = 100
@credit_card = credit_card('4000100011112224')
@declined_card = credit_card('1111111111111111')
@options = {
:order_id => (Time.now.to_i.to_s + "%015d" % rand(10000000000000000000000).to_s)[0,25],
:billing_address => address,
:description => 'Store Purchase'
}
end

def test_successful_purchase
assert response = @gateway.purchase(@amount, @credit_card, @options)
assert_success response
assert_equal '!', response.message
end

def test_unsuccessful_purchase
assert response = @gateway.purchase(@amount, @declined_card, @options)
assert_failure response
assert_equal ' no card no|no exp date|no brand', response.message
end

def test_authorize_and_capture
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert_equal '!', auth.message
assert auth.authorization
assert capture = @gateway.capture(@amount, auth.authorization)
assert_success capture
end

def test_unsuccessful_capture
assert response = @gateway.capture(@amount, '')
assert_failure response
assert_equal ' no orderid', response.message
end

def test_successful_void
assert auth = @gateway.authorize(@amount, @credit_card, @options)
assert_success auth
assert auth.authorization
assert void = @gateway.void(auth.authorization)
assert_equal '!', auth.message
assert_success void
end

def test_successful_referenced_credit
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
assert credit = @gateway.credit(@amount, purchase.authorization, @options)
assert_success credit
assert credit.authorization
assert_equal '!', credit.message
end

def test_unsuccessful_referenced_credit
assert purchase = @gateway.purchase(@amount, @credit_card, @options)
assert_success purchase
assert credit = @gateway.credit(@amount+1, purchase.authorization, @options) # too much refund requested
assert_failure credit
assert credit.authorization
assert_equal 'Overflow in refunds requests/1/1', credit.message
end

def test_successful_unreferenced_credit
assert credit = @gateway.credit(@amount, @credit_card, @options)
assert_success credit
assert credit.authorization
assert_equal '!', credit.message
end

def test_aliases
# Setting an alias
assert response = @gateway.purchase(@amount, credit_card('4000100011112224'), @options.merge(:alias=>"awesomeman",:order_id=>Time.now.to_i.to_s+"1"))
assert_success response
# Updating an alias
assert response = @gateway.purchase(@amount, credit_card('4111111111111111'), @options.merge(:alias=>"awesomeman",:order_id=>Time.now.to_i.to_s+"2"))
assert_success response
# Using an alias (i.e. don't provide the credit card)
assert response = @gateway.purchase(@amount, nil, @options.merge(:alias=>"awesomeman",:order_id=>Time.now.to_i.to_s+"3"))
assert_success response
end

def test_invalid_login
gateway = OgoneGateway.new(
:login => '',
:user => '',
:password => ''
)
assert response = gateway.purchase(@amount, @credit_card, @options)
assert_failure response
assert_equal ' no pspid', response.message
end

end

0 comments on commit 7efce06

Please sign in to comment.